@rancher/shell 3.0.12-rc.5 → 3.0.12-rc.6
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/apis/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/assets/images/providers/traefik.png +0 -0
- package/assets/translations/en-us.yaml +10 -4
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/GrafanaDashboard.vue +2 -8
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- package/components/form/Security.vue +1 -1
- package/components/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/config/labels-annotations.js +1 -0
- package/config/table-headers.js +5 -2
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +0 -2
- package/edit/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/pod.js +12 -10
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- package/package.json +1 -1
- package/pages/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +110 -0
- package/pages/c/_cluster/apps/charts/install.vue +29 -0
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- package/pages/c/_cluster/monitoring/index.vue +6 -1
- package/plugins/dashboard-store/resource-class.js +10 -3
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- package/store/__tests__/auth.test.ts +18 -0
- package/store/__tests__/notifications.test.ts +466 -1
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/auth.js +11 -1
- package/store/features.js +1 -0
- package/store/plugins.js +6 -0
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +58 -0
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/async.ts +2 -0
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- package/core/__tests__/plugin-products.test.ts +0 -4694
|
@@ -10,6 +10,7 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
10
10
|
let mockDispatch: jest.Mock;
|
|
11
11
|
let mockSchemaFor: jest.Mock;
|
|
12
12
|
let mockPaginationEnabled: jest.Mock;
|
|
13
|
+
let mockUrlFor: jest.Mock;
|
|
13
14
|
let consoleErrorSpy: any;
|
|
14
15
|
|
|
15
16
|
beforeEach(() => {
|
|
@@ -18,12 +19,23 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
18
19
|
mockDispatch = jest.fn() as any;
|
|
19
20
|
mockSchemaFor = jest.fn() as any;
|
|
20
21
|
mockPaginationEnabled = jest.fn() as any;
|
|
22
|
+
mockUrlFor = jest.fn() as any;
|
|
23
|
+
mockUrlFor.mockImplementation((type: string, id: string) => {
|
|
24
|
+
const schema = mockSchemaFor(type);
|
|
25
|
+
|
|
26
|
+
if (!schema) {
|
|
27
|
+
throw new Error(`No schema found for type "${ type }"`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return id ? `${ schema.linkFor() }/${ id }` : schema.linkFor();
|
|
31
|
+
});
|
|
21
32
|
|
|
22
33
|
mockStore = {
|
|
23
34
|
dispatch: mockDispatch,
|
|
24
35
|
getters: {
|
|
25
36
|
[`${ storeType }/schemaFor`]: mockSchemaFor,
|
|
26
|
-
[`${ storeType }/paginationEnabled`]: mockPaginationEnabled
|
|
37
|
+
[`${ storeType }/paginationEnabled`]: mockPaginationEnabled,
|
|
38
|
+
[`${ storeType }/urlFor`]: mockUrlFor
|
|
27
39
|
}
|
|
28
40
|
} as any;
|
|
29
41
|
|
|
@@ -49,7 +61,7 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
49
61
|
const result = await resourcesApi.find('pod', 'default/test-pod', { watch: true });
|
|
50
62
|
|
|
51
63
|
// Assert
|
|
52
|
-
expect(result).
|
|
64
|
+
expect(result).toMatchObject(mockResource);
|
|
53
65
|
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/find`, {
|
|
54
66
|
type: 'pod',
|
|
55
67
|
id: 'default/test-pod',
|
|
@@ -72,7 +84,7 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
72
84
|
const result = await resourcesApi.find('node', 'worker-1');
|
|
73
85
|
|
|
74
86
|
// Assert
|
|
75
|
-
expect(result).
|
|
87
|
+
expect(result).toMatchObject(mockResource);
|
|
76
88
|
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/find`, {
|
|
77
89
|
type: 'node',
|
|
78
90
|
id: 'worker-1',
|
|
@@ -89,7 +101,8 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
89
101
|
`Resource API error - ${ storeType } - Resource "pod" is namespaced. The resourceId must be in "namespace/name" format, but received "test-pod"`
|
|
90
102
|
);
|
|
91
103
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
92
|
-
`Resource API error - ${ storeType } - Resource "pod" is namespaced. The resourceId must be in "namespace/name" format, but received "test-pod"
|
|
104
|
+
`Resource API error - ${ storeType } - Resource "pod" is namespaced. The resourceId must be in "namespace/name" format, but received "test-pod"`,
|
|
105
|
+
undefined
|
|
93
106
|
);
|
|
94
107
|
expect(mockDispatch).not.toHaveBeenCalled();
|
|
95
108
|
});
|
|
@@ -105,7 +118,7 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
105
118
|
const result = await resourcesApi.find('custom.crd', 'my-resource');
|
|
106
119
|
|
|
107
120
|
// Assert
|
|
108
|
-
expect(result).
|
|
121
|
+
expect(result).toMatchObject(mockResource);
|
|
109
122
|
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/find`, {
|
|
110
123
|
type: 'custom.crd',
|
|
111
124
|
id: 'my-resource',
|
|
@@ -125,22 +138,11 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
125
138
|
`Resource API error - ${ storeType } - Failed to find resource pod/default/test-pod: Network error`
|
|
126
139
|
);
|
|
127
140
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
128
|
-
`Resource API error - ${ storeType } - Failed to find resource pod/default/test-pod: Network error
|
|
141
|
+
`Resource API error - ${ storeType } - Failed to find resource pod/default/test-pod: Network error`,
|
|
142
|
+
expect.any(Error)
|
|
129
143
|
);
|
|
130
144
|
});
|
|
131
145
|
|
|
132
|
-
it('should return null when dispatch returns undefined', async() => {
|
|
133
|
-
// Arrange
|
|
134
|
-
mockSchemaFor.mockReturnValue({ attributes: { namespaced: true } });
|
|
135
|
-
mockDispatch.mockResolvedValue(undefined);
|
|
136
|
-
|
|
137
|
-
// Act
|
|
138
|
-
const result = await resourcesApi.find('pod', 'default/nonexistent-pod');
|
|
139
|
-
|
|
140
|
-
// Assert
|
|
141
|
-
expect(result).toBeNull();
|
|
142
|
-
});
|
|
143
|
-
|
|
144
146
|
it('should handle resources with different types correctly', async() => {
|
|
145
147
|
// Arrange
|
|
146
148
|
const mockDeployment = {
|
|
@@ -155,7 +157,7 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
155
157
|
const result = await resourcesApi.find('apps.deployment', 'kube-system/test-deployment');
|
|
156
158
|
|
|
157
159
|
// Assert
|
|
158
|
-
expect(result).
|
|
160
|
+
expect(result).toMatchObject(mockDeployment);
|
|
159
161
|
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/find`, {
|
|
160
162
|
type: 'apps.deployment',
|
|
161
163
|
id: 'kube-system/test-deployment',
|
|
@@ -179,7 +181,9 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
179
181
|
const result = await resourcesApi.findFiltered('pod', { labelSelector });
|
|
180
182
|
|
|
181
183
|
// Assert
|
|
182
|
-
expect(result).
|
|
184
|
+
expect(result).toHaveLength(2);
|
|
185
|
+
expect((result as any[])[0]).toMatchObject(mockResources[0]);
|
|
186
|
+
expect((result as any[])[1]).toMatchObject(mockResources[1]);
|
|
183
187
|
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/findLabelSelector`, {
|
|
184
188
|
type: 'pod',
|
|
185
189
|
matching: {
|
|
@@ -271,7 +275,8 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
271
275
|
`Resource API error - ${ storeType } - Failed to find filtered resources pod: Network error`
|
|
272
276
|
);
|
|
273
277
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
274
|
-
`Resource API error - ${ storeType } - Failed to find filtered resources pod: Network error
|
|
278
|
+
`Resource API error - ${ storeType } - Failed to find filtered resources pod: Network error`,
|
|
279
|
+
expect.any(Error)
|
|
275
280
|
);
|
|
276
281
|
});
|
|
277
282
|
|
|
@@ -303,8 +308,9 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
303
308
|
} as any);
|
|
304
309
|
|
|
305
310
|
// Assert
|
|
306
|
-
expect(result).toStrictEqual(transientResponse);
|
|
307
311
|
expect(result).toHaveProperty('data');
|
|
312
|
+
expect((result as any).data).toHaveLength(1);
|
|
313
|
+
expect((result as any).data[0]).toMatchObject({ metadata: { name: 'pod-1' } });
|
|
308
314
|
});
|
|
309
315
|
|
|
310
316
|
it('should find resources with pagination options when enabled', async() => {
|
|
@@ -328,7 +334,9 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
328
334
|
const result = await resourcesApi.findFiltered('pod', paginationOptions as any);
|
|
329
335
|
|
|
330
336
|
// Assert
|
|
331
|
-
expect(result).
|
|
337
|
+
expect(result).toHaveLength(2);
|
|
338
|
+
expect((result as any[])[0]).toMatchObject(mockResources[0]);
|
|
339
|
+
expect((result as any[])[1]).toMatchObject(mockResources[1]);
|
|
332
340
|
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/findPage`, {
|
|
333
341
|
type: 'pod',
|
|
334
342
|
opt: paginationOptions
|
|
@@ -361,9 +369,10 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
361
369
|
const result = await resourcesApi.findFiltered('pod', paginationOptions as any);
|
|
362
370
|
|
|
363
371
|
// Assert
|
|
364
|
-
expect(result).toStrictEqual(transientResponse);
|
|
365
372
|
expect(result).toHaveProperty('data');
|
|
366
373
|
expect(result).toHaveProperty('pagination');
|
|
374
|
+
expect((result as any).data).toHaveLength(1);
|
|
375
|
+
expect((result as any).data[0]).toMatchObject({ metadata: { name: 'pod-1' } });
|
|
367
376
|
});
|
|
368
377
|
|
|
369
378
|
it('should throw error when pagination is not enabled', async() => {
|
|
@@ -382,7 +391,8 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
382
391
|
`Resource API error - ${ storeType } - findFiltered requests with FindFilteredPageOptions are only supported when ui-sql-cache is enabled`
|
|
383
392
|
);
|
|
384
393
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
385
|
-
`Resource API error - ${ storeType } - findFiltered requests with FindFilteredPageOptions are only supported when ui-sql-cache is enabled
|
|
394
|
+
`Resource API error - ${ storeType } - findFiltered requests with FindFilteredPageOptions are only supported when ui-sql-cache is enabled`,
|
|
395
|
+
undefined
|
|
386
396
|
);
|
|
387
397
|
expect(mockDispatch).not.toHaveBeenCalled();
|
|
388
398
|
});
|
|
@@ -396,7 +406,8 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
396
406
|
/findFiltered request was made with unknown options/
|
|
397
407
|
);
|
|
398
408
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
399
|
-
expect.stringContaining('findFiltered request was made with unknown options')
|
|
409
|
+
expect.stringContaining('findFiltered request was made with unknown options'),
|
|
410
|
+
undefined
|
|
400
411
|
);
|
|
401
412
|
});
|
|
402
413
|
|
|
@@ -442,7 +453,10 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
442
453
|
const result = await resourcesApi.findAll('pod');
|
|
443
454
|
|
|
444
455
|
// Assert
|
|
445
|
-
expect(result).
|
|
456
|
+
expect(result).toHaveLength(3);
|
|
457
|
+
expect((result as any[])[0]).toMatchObject(mockResources[0]);
|
|
458
|
+
expect((result as any[])[1]).toMatchObject(mockResources[1]);
|
|
459
|
+
expect((result as any[])[2]).toMatchObject(mockResources[2]);
|
|
446
460
|
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/findAll`, {
|
|
447
461
|
type: 'pod',
|
|
448
462
|
opt: {}
|
|
@@ -531,7 +545,8 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
531
545
|
`Resource API error - ${ storeType } - Failed to find all resources pod: Network error`
|
|
532
546
|
);
|
|
533
547
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
534
|
-
`Resource API error - ${ storeType } - Failed to find all resources pod: Network error
|
|
548
|
+
`Resource API error - ${ storeType } - Failed to find all resources pod: Network error`,
|
|
549
|
+
expect.any(Error)
|
|
535
550
|
);
|
|
536
551
|
});
|
|
537
552
|
|
|
@@ -547,4 +562,270 @@ describe.each(['cluster', 'management'] as const)('resourcesApiClassImpl with st
|
|
|
547
562
|
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
548
563
|
});
|
|
549
564
|
});
|
|
565
|
+
|
|
566
|
+
describe('create', () => {
|
|
567
|
+
it('should send a POST request with the resource data', async() => {
|
|
568
|
+
// Arrange
|
|
569
|
+
const inputData = {
|
|
570
|
+
type: 'configmap',
|
|
571
|
+
metadata: { name: 'my-config', namespace: 'default' },
|
|
572
|
+
};
|
|
573
|
+
const mockResponse = {
|
|
574
|
+
type: 'configmap',
|
|
575
|
+
metadata: { name: 'my-config', namespace: 'default' },
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
mockSchemaFor.mockReturnValue({
|
|
579
|
+
attributes: { namespaced: true },
|
|
580
|
+
linkFor: () => 'https://rancher/v1/configmaps'
|
|
581
|
+
});
|
|
582
|
+
mockDispatch.mockResolvedValue(mockResponse);
|
|
583
|
+
|
|
584
|
+
// Act
|
|
585
|
+
const result = await resourcesApi.create(inputData);
|
|
586
|
+
|
|
587
|
+
// Assert
|
|
588
|
+
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/request`, {
|
|
589
|
+
opt: {
|
|
590
|
+
url: 'https://rancher/v1/configmaps',
|
|
591
|
+
method: 'POST',
|
|
592
|
+
headers: { 'content-type': 'application/json' },
|
|
593
|
+
data: inputData,
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
expect(result).toStrictEqual(mockResponse);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('should throw when data.type is missing', async() => {
|
|
600
|
+
// Act & Assert
|
|
601
|
+
await expect(resourcesApi.create({} as any)).rejects.toThrow(
|
|
602
|
+
`Resource API error - ${ storeType } - Resource data must include a "type" property`
|
|
603
|
+
);
|
|
604
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it('should throw error and log when request fails', async() => {
|
|
608
|
+
// Arrange
|
|
609
|
+
mockSchemaFor.mockReturnValue({
|
|
610
|
+
attributes: { namespaced: false },
|
|
611
|
+
linkFor: () => 'https://rancher/v1/configmaps'
|
|
612
|
+
});
|
|
613
|
+
mockDispatch.mockRejectedValue(new Error('Request failed'));
|
|
614
|
+
|
|
615
|
+
// Act & Assert
|
|
616
|
+
await expect(resourcesApi.create({ type: 'configmap' })).rejects.toThrow(
|
|
617
|
+
`Resource API error - ${ storeType } - Failed to create resource of type "configmap": Request failed`
|
|
618
|
+
);
|
|
619
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
620
|
+
`Resource API error - ${ storeType } - Failed to create resource of type "configmap": Request failed`,
|
|
621
|
+
expect.any(Error)
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
describe('update', () => {
|
|
627
|
+
it('should send a PATCH request with strategic-merge-patch content type', async() => {
|
|
628
|
+
// Arrange
|
|
629
|
+
const mockResponse = { metadata: { name: 'my-config' }, data: { key: 'patched' } };
|
|
630
|
+
|
|
631
|
+
mockSchemaFor.mockReturnValue({
|
|
632
|
+
attributes: { namespaced: true },
|
|
633
|
+
linkFor: () => 'https://rancher/v1/configmaps'
|
|
634
|
+
});
|
|
635
|
+
mockDispatch.mockResolvedValue(mockResponse);
|
|
636
|
+
|
|
637
|
+
// Act
|
|
638
|
+
const result = await resourcesApi.update('configmap', 'default/my-config', { data: { key: 'patched' } });
|
|
639
|
+
|
|
640
|
+
// Assert
|
|
641
|
+
expect(result).toStrictEqual(mockResponse);
|
|
642
|
+
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/request`, {
|
|
643
|
+
opt: {
|
|
644
|
+
url: 'https://rancher/v1/configmaps/default/my-config',
|
|
645
|
+
method: 'patch',
|
|
646
|
+
headers: { 'content-type': 'application/strategic-merge-patch+json' },
|
|
647
|
+
data: { data: { key: 'patched' } },
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it('should throw error for namespaced resource without namespace in id', async() => {
|
|
653
|
+
// Arrange
|
|
654
|
+
mockSchemaFor.mockReturnValue({ attributes: { namespaced: true } });
|
|
655
|
+
|
|
656
|
+
// Act & Assert
|
|
657
|
+
await expect(resourcesApi.update('configmap', 'my-config', {})).rejects.toThrow(
|
|
658
|
+
`Resource API error - ${ storeType } - Resource "configmap" is namespaced. The resourceId must be in "namespace/name" format, but received "my-config"`
|
|
659
|
+
);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('should throw error when no schema is found', async() => {
|
|
663
|
+
// Arrange
|
|
664
|
+
mockSchemaFor.mockReturnValue(null);
|
|
665
|
+
|
|
666
|
+
// Act & Assert
|
|
667
|
+
await expect(resourcesApi.update('configmap', 'default/my-config', {})).rejects.toThrow(
|
|
668
|
+
'No schema found for type "configmap"'
|
|
669
|
+
);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it('should throw error and log when request fails', async() => {
|
|
673
|
+
// Arrange
|
|
674
|
+
mockSchemaFor.mockReturnValue({
|
|
675
|
+
attributes: { namespaced: false },
|
|
676
|
+
linkFor: () => 'https://rancher/v1/nodes'
|
|
677
|
+
});
|
|
678
|
+
mockDispatch.mockRejectedValue(new Error('Network error'));
|
|
679
|
+
|
|
680
|
+
// Act & Assert
|
|
681
|
+
await expect(resourcesApi.update('node', 'worker-1', {})).rejects.toThrow(
|
|
682
|
+
`Resource API error - ${ storeType } - Failed to update resource node/worker-1: Network error`
|
|
683
|
+
);
|
|
684
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
685
|
+
`Resource API error - ${ storeType } - Failed to update resource node/worker-1: Network error`,
|
|
686
|
+
expect.any(Error)
|
|
687
|
+
);
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
describe('replace', () => {
|
|
692
|
+
it('should send a PUT request with cleanForSave applied', async() => {
|
|
693
|
+
// Arrange
|
|
694
|
+
const inputData = {
|
|
695
|
+
type: 'configmap',
|
|
696
|
+
metadata: { name: 'my-config', namespace: 'default' },
|
|
697
|
+
data: { key: 'value' },
|
|
698
|
+
};
|
|
699
|
+
const cleanedData = { ...inputData };
|
|
700
|
+
const mockModel = { cleanForSave: jest.fn<any, any[]>().mockReturnValue(cleanedData) };
|
|
701
|
+
const mockResponse = { ...inputData, metadata: { ...inputData.metadata, resourceVersion: '2' } };
|
|
702
|
+
|
|
703
|
+
mockSchemaFor.mockReturnValue({
|
|
704
|
+
attributes: { namespaced: true },
|
|
705
|
+
linkFor: () => 'https://rancher/v1/configmaps'
|
|
706
|
+
});
|
|
707
|
+
// First dispatch: create (returns model), second dispatch: request (returns response)
|
|
708
|
+
mockDispatch
|
|
709
|
+
.mockResolvedValueOnce(mockModel)
|
|
710
|
+
.mockResolvedValueOnce(mockResponse);
|
|
711
|
+
|
|
712
|
+
// Act
|
|
713
|
+
const result = await resourcesApi.replace('configmap', 'default/my-config', inputData);
|
|
714
|
+
|
|
715
|
+
// Assert
|
|
716
|
+
expect(result).toStrictEqual(mockResponse);
|
|
717
|
+
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/create`, inputData);
|
|
718
|
+
expect(mockModel.cleanForSave).toHaveBeenCalledWith({ ...inputData });
|
|
719
|
+
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/request`, {
|
|
720
|
+
opt: {
|
|
721
|
+
url: 'https://rancher/v1/configmaps/default/my-config',
|
|
722
|
+
method: 'put',
|
|
723
|
+
headers: { 'content-type': 'application/json' },
|
|
724
|
+
data: cleanedData,
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it('should throw error for namespaced resource without namespace in id', async() => {
|
|
730
|
+
// Arrange
|
|
731
|
+
mockSchemaFor.mockReturnValue({ attributes: { namespaced: true } });
|
|
732
|
+
|
|
733
|
+
// Act & Assert
|
|
734
|
+
await expect(resourcesApi.replace('configmap', 'my-config', {})).rejects.toThrow(
|
|
735
|
+
`Resource API error - ${ storeType } - Resource "configmap" is namespaced. The resourceId must be in "namespace/name" format, but received "my-config"`
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
it('should throw error and log when request fails', async() => {
|
|
740
|
+
// Arrange
|
|
741
|
+
const mockModel = { cleanForSave: jest.fn<any, any[]>().mockReturnValue({}) };
|
|
742
|
+
|
|
743
|
+
mockSchemaFor.mockReturnValue({
|
|
744
|
+
attributes: { namespaced: false },
|
|
745
|
+
linkFor: () => 'https://rancher/v1/nodes'
|
|
746
|
+
});
|
|
747
|
+
mockDispatch
|
|
748
|
+
.mockResolvedValueOnce(mockModel)
|
|
749
|
+
.mockRejectedValueOnce(new Error('Conflict'));
|
|
750
|
+
|
|
751
|
+
// Act & Assert
|
|
752
|
+
await expect(resourcesApi.replace('node', 'worker-1', {})).rejects.toThrow(
|
|
753
|
+
`Resource API error - ${ storeType } - Failed to update resource node/worker-1: Conflict`
|
|
754
|
+
);
|
|
755
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
756
|
+
`Resource API error - ${ storeType } - Failed to update resource node/worker-1: Conflict`,
|
|
757
|
+
expect.any(Error)
|
|
758
|
+
);
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
describe('delete', () => {
|
|
763
|
+
it('should send a DELETE request with the correct URL', async() => {
|
|
764
|
+
// Arrange
|
|
765
|
+
mockSchemaFor.mockReturnValue({
|
|
766
|
+
attributes: { namespaced: true },
|
|
767
|
+
linkFor: () => 'https://rancher/v1/configmaps'
|
|
768
|
+
});
|
|
769
|
+
mockDispatch.mockResolvedValue(undefined);
|
|
770
|
+
|
|
771
|
+
// Act
|
|
772
|
+
await resourcesApi.delete('configmap', 'default/my-config');
|
|
773
|
+
|
|
774
|
+
// Assert
|
|
775
|
+
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/request`, {
|
|
776
|
+
opt: {
|
|
777
|
+
url: 'https://rancher/v1/configmaps/default/my-config',
|
|
778
|
+
method: 'delete',
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
it('should work with cluster-scoped resources', async() => {
|
|
784
|
+
// Arrange
|
|
785
|
+
mockSchemaFor.mockReturnValue({
|
|
786
|
+
attributes: { namespaced: false },
|
|
787
|
+
linkFor: () => 'https://rancher/v1/nodes'
|
|
788
|
+
});
|
|
789
|
+
mockDispatch.mockResolvedValue(undefined);
|
|
790
|
+
|
|
791
|
+
// Act
|
|
792
|
+
await resourcesApi.delete('node', 'worker-1');
|
|
793
|
+
|
|
794
|
+
// Assert
|
|
795
|
+
expect(mockDispatch).toHaveBeenCalledWith(`${ storeType }/request`, {
|
|
796
|
+
opt: {
|
|
797
|
+
url: 'https://rancher/v1/nodes/worker-1',
|
|
798
|
+
method: 'delete',
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it('should throw error for namespaced resource without namespace in id', async() => {
|
|
804
|
+
// Arrange
|
|
805
|
+
mockSchemaFor.mockReturnValue({ attributes: { namespaced: true } });
|
|
806
|
+
|
|
807
|
+
// Act & Assert
|
|
808
|
+
await expect(resourcesApi.delete('configmap', 'my-config')).rejects.toThrow(
|
|
809
|
+
`Resource API error - ${ storeType } - Resource "configmap" is namespaced. The resourceId must be in "namespace/name" format, but received "my-config"`
|
|
810
|
+
);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
it('should throw error and log when request fails', async() => {
|
|
814
|
+
// Arrange
|
|
815
|
+
mockSchemaFor.mockReturnValue({
|
|
816
|
+
attributes: { namespaced: false },
|
|
817
|
+
linkFor: () => 'https://rancher/v1/nodes'
|
|
818
|
+
});
|
|
819
|
+
mockDispatch.mockRejectedValue(new Error('Not Found'));
|
|
820
|
+
|
|
821
|
+
// Act & Assert
|
|
822
|
+
await expect(resourcesApi.delete('node', 'worker-1')).rejects.toThrow(
|
|
823
|
+
`Resource API error - ${ storeType } - Failed to delete resource node/worker-1: Not Found`
|
|
824
|
+
);
|
|
825
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
826
|
+
`Resource API error - ${ storeType } - Failed to delete resource node/worker-1: Not Found`,
|
|
827
|
+
expect.any(Error)
|
|
828
|
+
);
|
|
829
|
+
});
|
|
830
|
+
});
|
|
550
831
|
});
|