@scality/data-browser-library 1.0.5 → 1.0.7

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.
@@ -4,6 +4,7 @@ import user_event from "@testing-library/user-event";
4
4
  import { MemoryRouter, Route, Routes } from "react-router";
5
5
  import { useGetBucketReplication, useSetBucketReplication } from "../../hooks/bucketConfiguration.js";
6
6
  import { useBuckets } from "../../hooks/bucketOperations.js";
7
+ import { useISVBucketStatus } from "../../hooks/useISVBucketDetection.js";
7
8
  import { createTestWrapper, findToggleByLabel, mockErrorSubmit, mockOffsetSize, mockSuccessSubmit, submitForm } from "../../test/testUtils.js";
8
9
  import { BucketReplicationFormPage } from "../buckets/BucketReplicationFormPage.js";
9
10
  jest.mock('../../hooks/bucketConfiguration', ()=>({
@@ -13,9 +14,13 @@ jest.mock('../../hooks/bucketConfiguration', ()=>({
13
14
  jest.mock('../../hooks/bucketOperations', ()=>({
14
15
  useBuckets: jest.fn()
15
16
  }));
17
+ jest.mock('../../hooks/useISVBucketDetection', ()=>({
18
+ useISVBucketStatus: jest.fn()
19
+ }));
16
20
  const mockUseGetBucketReplication = jest.mocked(useGetBucketReplication);
17
21
  const mockUseSetBucketReplication = jest.mocked(useSetBucketReplication);
18
22
  const mockUseBuckets = jest.mocked(useBuckets);
23
+ const mockUseISVBucketStatus = jest.mocked(useISVBucketStatus);
19
24
  const mockNavigate = jest.fn();
20
25
  const mockShowToast = jest.fn();
21
26
  jest.mock('react-router', ()=>({
@@ -55,8 +60,12 @@ describe('BucketReplicationFormPage', ()=>{
55
60
  const mockMutate = jest.fn();
56
61
  const fillRequiredFields = async (options)=>{
57
62
  const { roleArn = 'arn:aws:iam::123456789012:role/replication-role', ruleId, targetBucket = 'destination-bucket' } = options;
58
- await user_event.type(screen.getByLabelText(/role arn/i), roleArn);
59
- await user_event.type(screen.getByLabelText(/rule id/i), ruleId);
63
+ await user_event.type(screen.getByRole('textbox', {
64
+ name: /role arn/i
65
+ }), roleArn);
66
+ await user_event.type(screen.getByRole('textbox', {
67
+ name: /rule id/i
68
+ }), ruleId);
60
69
  const targetBucketSelect = screen.getByLabelText(/target bucket/i);
61
70
  await user_event.click(targetBucketSelect);
62
71
  await user_event.click(screen.getByRole('option', {
@@ -92,6 +101,15 @@ describe('BucketReplicationFormPage', ()=>{
92
101
  },
93
102
  status: 'success'
94
103
  });
104
+ mockUseISVBucketStatus.mockReturnValue({
105
+ isVeeamBucket: false,
106
+ isCommvaultBucket: false,
107
+ isKastenBucket: false,
108
+ isISVManaged: false,
109
+ isvApplication: void 0,
110
+ isLoading: false,
111
+ bucketTagsStatus: 'success'
112
+ });
95
113
  });
96
114
  describe('Page Rendering', ()=>{
97
115
  it('renders create mode with correct title', ()=>{
@@ -147,8 +165,12 @@ describe('BucketReplicationFormPage', ()=>{
147
165
  it('displays all form sections', async ()=>{
148
166
  renderBucketReplicationFormPage();
149
167
  await waitFor(()=>{
150
- expect(screen.getByLabelText(/role arn/i)).toBeInTheDocument();
151
- expect(screen.getByLabelText(/rule id/i)).toBeInTheDocument();
168
+ expect(screen.getByRole('textbox', {
169
+ name: /role arn/i
170
+ })).toBeInTheDocument();
171
+ expect(screen.getByRole('textbox', {
172
+ name: /rule id/i
173
+ })).toBeInTheDocument();
152
174
  expect(screen.getByText('Replicate encrypted objects')).toBeInTheDocument();
153
175
  expect(screen.getByText('Delete marker replication')).toBeInTheDocument();
154
176
  });
@@ -157,14 +179,20 @@ describe('BucketReplicationFormPage', ()=>{
157
179
  describe('Form Fields - Initial State', ()=>{
158
180
  it('renders required form fields in create mode', ()=>{
159
181
  renderBucketReplicationFormPage();
160
- expect(screen.getByLabelText(/role arn/i)).toBeInTheDocument();
161
- expect(screen.getByLabelText(/rule id/i)).toBeInTheDocument();
182
+ expect(screen.getByRole('textbox', {
183
+ name: /role arn/i
184
+ })).toBeInTheDocument();
185
+ expect(screen.getByRole('textbox', {
186
+ name: /rule id/i
187
+ })).toBeInTheDocument();
162
188
  expect(screen.getByLabelText(/status/i)).toBeInTheDocument();
163
189
  expect(screen.getByLabelText(/target bucket/i)).toBeInTheDocument();
164
190
  });
165
191
  it('shows Role ARN input when no existing rules', ()=>{
166
192
  renderBucketReplicationFormPage();
167
- const roleInput = screen.getByLabelText(/role arn/i);
193
+ const roleInput = screen.getByRole('textbox', {
194
+ name: /role arn/i
195
+ });
168
196
  expect(roleInput).toBeInTheDocument();
169
197
  expect(roleInput.tagName).toBe('INPUT');
170
198
  });
@@ -194,7 +222,9 @@ describe('BucketReplicationFormPage', ()=>{
194
222
  });
195
223
  it('shows Rule ID input in create mode', ()=>{
196
224
  renderBucketReplicationFormPage();
197
- const ruleIdInput = screen.getByLabelText(/rule id/i);
225
+ const ruleIdInput = screen.getByRole('textbox', {
226
+ name: /rule id/i
227
+ });
198
228
  expect(ruleIdInput).toBeInTheDocument();
199
229
  expect(ruleIdInput.tagName).toBe('INPUT');
200
230
  });
@@ -229,14 +259,18 @@ describe('BucketReplicationFormPage', ()=>{
229
259
  it('renders Status select field', async ()=>{
230
260
  renderBucketReplicationFormPage();
231
261
  await waitFor(()=>{
232
- expect(screen.getByLabelText(/rule id/i)).toBeInTheDocument();
262
+ expect(screen.getByRole('textbox', {
263
+ name: /rule id/i
264
+ })).toBeInTheDocument();
233
265
  });
234
266
  const statusElement = document.querySelector('[id="status"]');
235
267
  expect(statusElement).toBeInTheDocument();
236
268
  });
237
269
  it('renders Priority number input with auto-assigned placeholder', ()=>{
238
270
  renderBucketReplicationFormPage();
239
- const priorityInput = screen.getByLabelText(/rule priority/i);
271
+ const priorityInput = screen.getByRole('spinbutton', {
272
+ name: /rule priority/i
273
+ });
240
274
  expect(priorityInput).toBeInTheDocument();
241
275
  expect(priorityInput).toHaveAttribute('placeholder', 'Example: Auto-assigned: 0');
242
276
  });
@@ -255,7 +289,9 @@ describe('BucketReplicationFormPage', ()=>{
255
289
  });
256
290
  it('renders storage class select with all options', async ()=>{
257
291
  renderBucketReplicationFormPage();
258
- const storageClassSelect = screen.getByLabelText(/storage class/i);
292
+ const storageClassSelect = screen.getByLabelText(/storage class/i, {
293
+ selector: 'input'
294
+ });
259
295
  await user_event.click(storageClassSelect);
260
296
  expect(screen.getByRole('option', {
261
297
  name: 'Same as source'
@@ -283,7 +319,9 @@ describe('BucketReplicationFormPage', ()=>{
283
319
  describe('Form Validation', ()=>{
284
320
  it('validates Role ARN is required for first replication rule', async ()=>{
285
321
  renderBucketReplicationFormPage();
286
- const roleInput = screen.getByLabelText(/role arn/i);
322
+ const roleInput = screen.getByRole('textbox', {
323
+ name: /role arn/i
324
+ });
287
325
  await user_event.type(roleInput, 'test');
288
326
  await user_event.clear(roleInput);
289
327
  await waitFor(()=>{
@@ -310,7 +348,9 @@ describe('BucketReplicationFormPage', ()=>{
310
348
  status: 'success'
311
349
  });
312
350
  renderBucketReplicationFormPage();
313
- await user_event.type(screen.getByLabelText(/rule id/i), 'new-rule');
351
+ await user_event.type(screen.getByRole('textbox', {
352
+ name: /rule id/i
353
+ }), 'new-rule');
314
354
  const targetBucketSelect = screen.getByLabelText(/target bucket/i);
315
355
  await user_event.click(targetBucketSelect);
316
356
  await user_event.click(screen.getByRole('option', {
@@ -325,7 +365,9 @@ describe('BucketReplicationFormPage', ()=>{
325
365
  });
326
366
  it('validates Rule ID is required', async ()=>{
327
367
  renderBucketReplicationFormPage();
328
- const ruleIdInput = screen.getByLabelText(/rule id/i);
368
+ const ruleIdInput = screen.getByRole('textbox', {
369
+ name: /rule id/i
370
+ });
329
371
  await user_event.type(ruleIdInput, 'test');
330
372
  await user_event.clear(ruleIdInput);
331
373
  await waitFor(()=>{
@@ -352,7 +394,9 @@ describe('BucketReplicationFormPage', ()=>{
352
394
  status: 'success'
353
395
  });
354
396
  renderBucketReplicationFormPage();
355
- const ruleIdInput = screen.getByLabelText(/rule id/i);
397
+ const ruleIdInput = screen.getByRole('textbox', {
398
+ name: /rule id/i
399
+ });
356
400
  await user_event.type(ruleIdInput, 'existing-rule');
357
401
  await waitFor(()=>{
358
402
  expect(screen.getByText(/a rule with this id already exists/i)).toBeInTheDocument();
@@ -360,8 +404,12 @@ describe('BucketReplicationFormPage', ()=>{
360
404
  });
361
405
  it('validates Priority must be >= 0', async ()=>{
362
406
  renderBucketReplicationFormPage();
363
- await user_event.type(screen.getByLabelText(/rule id/i), 'test-rule');
364
- const priorityInput = screen.getByLabelText(/rule priority/i);
407
+ await user_event.type(screen.getByRole('textbox', {
408
+ name: /rule id/i
409
+ }), 'test-rule');
410
+ const priorityInput = screen.getByRole('spinbutton', {
411
+ name: /rule priority/i
412
+ });
365
413
  await user_event.clear(priorityInput);
366
414
  await user_event.type(priorityInput, '-1');
367
415
  await user_event.tab();
@@ -371,8 +419,12 @@ describe('BucketReplicationFormPage', ()=>{
371
419
  });
372
420
  it('disables submit button when Target Bucket is not selected', async ()=>{
373
421
  renderBucketReplicationFormPage();
374
- await user_event.type(screen.getByLabelText(/role arn/i), 'arn:aws:iam::123456789012:role/role');
375
- await user_event.type(screen.getByLabelText(/rule id/i), 'test-rule');
422
+ await user_event.type(screen.getByRole('textbox', {
423
+ name: /role arn/i
424
+ }), 'arn:aws:iam::123456789012:role/role');
425
+ await user_event.type(screen.getByRole('textbox', {
426
+ name: /rule id/i
427
+ }), 'test-rule');
376
428
  await waitFor(()=>{
377
429
  const createButton = screen.getByRole('button', {
378
430
  name: /create/i
@@ -385,7 +437,9 @@ describe('BucketReplicationFormPage', ()=>{
385
437
  const sameAccountToggle = findToggleByLabel('Same account destination');
386
438
  await user_event.click(sameAccountToggle);
387
439
  await waitFor(()=>{
388
- expect(screen.getByLabelText(/target account id/i)).toBeInTheDocument();
440
+ expect(screen.getByRole('textbox', {
441
+ name: /target account id/i
442
+ })).toBeInTheDocument();
389
443
  });
390
444
  });
391
445
  it('shows Replica KMS Key ID field when encryptReplicatedObjects is true', async ()=>{
@@ -393,7 +447,9 @@ describe('BucketReplicationFormPage', ()=>{
393
447
  const encryptToggle = findToggleByLabel('Encrypt replicated objects');
394
448
  await user_event.click(encryptToggle);
395
449
  await waitFor(()=>{
396
- expect(screen.getByLabelText(/replica kms key id/i)).toBeInTheDocument();
450
+ expect(screen.getByRole('textbox', {
451
+ name: /replica kms key id/i
452
+ })).toBeInTheDocument();
397
453
  });
398
454
  });
399
455
  it('shows prefix field when filterType is prefix', async ()=>{
@@ -409,7 +465,9 @@ describe('BucketReplicationFormPage', ()=>{
409
465
  name: 'Prefix filter'
410
466
  }));
411
467
  await waitFor(()=>{
412
- expect(screen.getByLabelText(/prefix/i)).toBeInTheDocument();
468
+ expect(screen.getByRole('textbox', {
469
+ name: /prefix/i
470
+ })).toBeInTheDocument();
413
471
  });
414
472
  });
415
473
  });
@@ -505,7 +563,9 @@ describe('BucketReplicationFormPage', ()=>{
505
563
  const sameAccountToggle = findToggleByLabel('Same account destination');
506
564
  await user_event.click(sameAccountToggle);
507
565
  await waitFor(()=>{
508
- expect(screen.getByLabelText(/target account id/i)).toBeInTheDocument();
566
+ expect(screen.getByRole('textbox', {
567
+ name: /target account id/i
568
+ })).toBeInTheDocument();
509
569
  });
510
570
  });
511
571
  it('renders target bucket as Input for cross-account replication', async ()=>{
@@ -523,7 +583,9 @@ describe('BucketReplicationFormPage', ()=>{
523
583
  const encryptToggle = findToggleByLabel('Encrypt replicated objects');
524
584
  await user_event.click(encryptToggle);
525
585
  await waitFor(()=>{
526
- expect(screen.getByLabelText(/replica kms key id/i)).toBeInTheDocument();
586
+ expect(screen.getByRole('textbox', {
587
+ name: /replica kms key id/i
588
+ })).toBeInTheDocument();
527
589
  });
528
590
  });
529
591
  });
@@ -556,9 +618,15 @@ describe('BucketReplicationFormPage', ()=>{
556
618
  });
557
619
  it('submits complete replication rule with all optional fields', async ()=>{
558
620
  renderBucketReplicationFormPage();
559
- await user_event.type(screen.getByLabelText(/role arn/i), 'arn:aws:iam::123456789012:role/replication-role');
560
- await user_event.type(screen.getByLabelText(/rule id/i), 'complete-rule');
561
- await user_event.type(screen.getByLabelText(/rule priority/i), '5');
621
+ await user_event.type(screen.getByRole('textbox', {
622
+ name: /role arn/i
623
+ }), 'arn:aws:iam::123456789012:role/replication-role');
624
+ await user_event.type(screen.getByRole('textbox', {
625
+ name: /rule id/i
626
+ }), 'complete-rule');
627
+ await user_event.type(screen.getByRole('spinbutton', {
628
+ name: /rule priority/i
629
+ }), '5');
562
630
  const filterSelect = document.querySelector('[id="filterType"]');
563
631
  await user_event.click(filterSelect);
564
632
  await waitFor(()=>{
@@ -570,15 +638,23 @@ describe('BucketReplicationFormPage', ()=>{
570
638
  name: 'Prefix filter'
571
639
  }));
572
640
  await waitFor(()=>{
573
- expect(screen.getByLabelText(/prefix/i)).toBeInTheDocument();
641
+ expect(screen.getByRole('textbox', {
642
+ name: /prefix/i
643
+ })).toBeInTheDocument();
574
644
  });
575
- await user_event.type(screen.getByLabelText(/prefix/i), 'logs/');
645
+ await user_event.type(screen.getByRole('textbox', {
646
+ name: /prefix/i
647
+ }), 'logs/');
576
648
  const sameAccountToggle = findToggleByLabel('Same account destination');
577
649
  await user_event.click(sameAccountToggle);
578
650
  await waitFor(()=>{
579
- expect(screen.getByLabelText(/target account id/i)).toBeInTheDocument();
651
+ expect(screen.getByRole('textbox', {
652
+ name: /target account id/i
653
+ })).toBeInTheDocument();
580
654
  });
581
- await user_event.type(screen.getByLabelText(/target account id/i), '987654321098');
655
+ await user_event.type(screen.getByRole('textbox', {
656
+ name: /target account id/i
657
+ }), '987654321098');
582
658
  await user_event.type(screen.getByLabelText(/target bucket/i), 'cross-account-bucket');
583
659
  const storageClassSelect = document.querySelector('[id="storageClass"]');
584
660
  await user_event.click(storageClassSelect);
@@ -595,9 +671,13 @@ describe('BucketReplicationFormPage', ()=>{
595
671
  const includeEncryptedToggle = findToggleByLabel('Replicate encrypted objects');
596
672
  await user_event.click(includeEncryptedToggle);
597
673
  await waitFor(()=>{
598
- expect(screen.getByLabelText(/replica kms key id/i)).toBeInTheDocument();
674
+ expect(screen.getByRole('textbox', {
675
+ name: /replica kms key id/i
676
+ })).toBeInTheDocument();
599
677
  });
600
- await user_event.type(screen.getByLabelText(/replica kms key id/i), 'arn:aws:kms:us-east-1:987654321098:key/12345678-1234-1234-1234-123456789012');
678
+ await user_event.type(screen.getByRole('textbox', {
679
+ name: /replica kms key id/i
680
+ }), 'arn:aws:kms:us-east-1:987654321098:key/12345678-1234-1234-1234-123456789012');
601
681
  const enforceRTCToggle = findToggleByLabel('Replication Time Control (RTC)');
602
682
  await user_event.click(enforceRTCToggle);
603
683
  const replicaModToggle = findToggleByLabel('Replica modification sync');
@@ -674,7 +754,9 @@ describe('BucketReplicationFormPage', ()=>{
674
754
  status: 'success'
675
755
  });
676
756
  renderBucketReplicationFormPage();
677
- await user_event.type(screen.getByLabelText(/rule id/i), 'new-rule');
757
+ await user_event.type(screen.getByRole('textbox', {
758
+ name: /rule id/i
759
+ }), 'new-rule');
678
760
  const targetBucketSelect = screen.getByLabelText(/target bucket/i);
679
761
  await user_event.click(targetBucketSelect);
680
762
  await user_event.click(screen.getByRole('option', {
@@ -753,7 +835,9 @@ describe('BucketReplicationFormPage', ()=>{
753
835
  });
754
836
  it('disables submit button when form is invalid', async ()=>{
755
837
  renderBucketReplicationFormPage();
756
- await user_event.type(screen.getByLabelText(/rule id/i), 'test');
838
+ await user_event.type(screen.getByRole('textbox', {
839
+ name: /rule id/i
840
+ }), 'test');
757
841
  await waitFor(()=>{
758
842
  const createButton = screen.getByRole('button', {
759
843
  name: /create/i
@@ -1033,7 +1117,9 @@ describe('BucketReplicationFormPage', ()=>{
1033
1117
  });
1034
1118
  renderBucketReplicationFormPage('test-bucket', 'prefix-rule');
1035
1119
  await waitFor(()=>{
1036
- const prefixInput = screen.getByLabelText(/prefix/i);
1120
+ const prefixInput = screen.getByRole('textbox', {
1121
+ name: /prefix/i
1122
+ });
1037
1123
  expect(prefixInput).toHaveValue('documents/');
1038
1124
  });
1039
1125
  });
@@ -1106,7 +1192,9 @@ describe('BucketReplicationFormPage', ()=>{
1106
1192
  });
1107
1193
  renderBucketReplicationFormPage('test-bucket', 'and-rule');
1108
1194
  await waitFor(()=>{
1109
- const prefixInput = screen.getByLabelText(/prefix/i);
1195
+ const prefixInput = screen.getByRole('textbox', {
1196
+ name: /prefix/i
1197
+ });
1110
1198
  expect(prefixInput).toHaveValue('logs/');
1111
1199
  expect(screen.getByDisplayValue('type')).toBeInTheDocument();
1112
1200
  expect(screen.getByDisplayValue('audit')).toBeInTheDocument();
@@ -1172,7 +1260,9 @@ describe('BucketReplicationFormPage', ()=>{
1172
1260
  await waitFor(()=>{
1173
1261
  const encryptReplicatedToggle = findToggleByLabel('Encrypt replicated objects');
1174
1262
  expect(encryptReplicatedToggle).toBeChecked();
1175
- const kmsKeyInput = screen.getByLabelText(/replica kms key id/i);
1263
+ const kmsKeyInput = screen.getByRole('textbox', {
1264
+ name: /replica kms key id/i
1265
+ });
1176
1266
  expect(kmsKeyInput).toHaveValue('arn:aws:kms:us-east-1:123456789012:key/12345');
1177
1267
  });
1178
1268
  });
@@ -1300,7 +1390,9 @@ describe('BucketReplicationFormPage', ()=>{
1300
1390
  await waitFor(()=>{
1301
1391
  const sameAccountToggle = screen.getByText(/not same account destination/i);
1302
1392
  expect(sameAccountToggle).toBeInTheDocument();
1303
- const accountIdInput = screen.getByLabelText(/target account id/i);
1393
+ const accountIdInput = screen.getByRole('textbox', {
1394
+ name: /target account id/i
1395
+ });
1304
1396
  expect(accountIdInput).toHaveValue('987654321098');
1305
1397
  const targetBucketInput = screen.getByLabelText(/target bucket/i);
1306
1398
  expect(targetBucketInput).toHaveValue('cross-account-bucket');
@@ -1359,7 +1451,9 @@ describe('BucketReplicationFormPage', ()=>{
1359
1451
  });
1360
1452
  renderBucketReplicationFormPage('test-bucket', 'storage-class-rule');
1361
1453
  await waitFor(()=>{
1362
- const storageClassSelect = screen.getByLabelText(/storage class/i);
1454
+ const storageClassSelect = screen.getByLabelText(/storage class/i, {
1455
+ selector: 'input'
1456
+ });
1363
1457
  expect(storageClassSelect).toBeInTheDocument();
1364
1458
  });
1365
1459
  });
@@ -1385,7 +1479,9 @@ describe('BucketReplicationFormPage', ()=>{
1385
1479
  });
1386
1480
  renderBucketReplicationFormPage('test-bucket', 'priority-rule');
1387
1481
  await waitFor(()=>{
1388
- const priorityInput = screen.getByLabelText(/rule priority/i);
1482
+ const priorityInput = screen.getByRole('spinbutton', {
1483
+ name: /rule priority/i
1484
+ });
1389
1485
  expect(priorityInput).toHaveValue(99);
1390
1486
  });
1391
1487
  });
@@ -1393,7 +1489,9 @@ describe('BucketReplicationFormPage', ()=>{
1393
1489
  describe('Priority Auto-assignment', ()=>{
1394
1490
  it('auto-assigns priority 0 when no existing rules', ()=>{
1395
1491
  renderBucketReplicationFormPage();
1396
- const priorityInput = screen.getByLabelText(/rule priority/i);
1492
+ const priorityInput = screen.getByRole('spinbutton', {
1493
+ name: /rule priority/i
1494
+ });
1397
1495
  expect(priorityInput).toHaveAttribute('placeholder', 'Example: Auto-assigned: 0');
1398
1496
  });
1399
1497
  it('auto-assigns next available priority when rules exist', ()=>{
@@ -1424,19 +1522,27 @@ describe('BucketReplicationFormPage', ()=>{
1424
1522
  status: 'success'
1425
1523
  });
1426
1524
  renderBucketReplicationFormPage();
1427
- const priorityInput = screen.getByLabelText(/rule priority/i);
1525
+ const priorityInput = screen.getByRole('spinbutton', {
1526
+ name: /rule priority/i
1527
+ });
1428
1528
  expect(priorityInput).toHaveAttribute('placeholder', 'Example: Auto-assigned: 11');
1429
1529
  });
1430
1530
  it('allows manual priority override', async ()=>{
1431
1531
  renderBucketReplicationFormPage();
1432
- const priorityInput = screen.getByLabelText(/rule priority/i);
1532
+ const priorityInput = screen.getByRole('spinbutton', {
1533
+ name: /rule priority/i
1534
+ });
1433
1535
  await user_event.type(priorityInput, '25');
1434
1536
  expect(priorityInput).toHaveValue(25);
1435
1537
  });
1436
1538
  it('accepts empty priority and defaults to 0 on submit', async ()=>{
1437
1539
  renderBucketReplicationFormPage();
1438
- await user_event.type(screen.getByLabelText(/role arn/i), 'arn:aws:iam::123456789012:role/role');
1439
- await user_event.type(screen.getByLabelText(/rule id/i), 'no-priority-rule');
1540
+ await user_event.type(screen.getByRole('textbox', {
1541
+ name: /role arn/i
1542
+ }), 'arn:aws:iam::123456789012:role/role');
1543
+ await user_event.type(screen.getByRole('textbox', {
1544
+ name: /rule id/i
1545
+ }), 'no-priority-rule');
1440
1546
  const targetBucketSelect = screen.getByLabelText(/target bucket/i);
1441
1547
  await user_event.click(targetBucketSelect);
1442
1548
  await user_event.click(screen.getByRole('option', {
@@ -1520,8 +1626,12 @@ describe('BucketReplicationFormPage', ()=>{
1520
1626
  isPending: true
1521
1627
  });
1522
1628
  renderBucketReplicationFormPage();
1523
- await user_event.type(screen.getByLabelText(/role arn/i), 'arn:aws:iam::123456789012:role/role');
1524
- await user_event.type(screen.getByLabelText(/rule id/i), 'test-rule');
1629
+ await user_event.type(screen.getByRole('textbox', {
1630
+ name: /role arn/i
1631
+ }), 'arn:aws:iam::123456789012:role/role');
1632
+ await user_event.type(screen.getByRole('textbox', {
1633
+ name: /rule id/i
1634
+ }), 'test-rule');
1525
1635
  const targetBucketSelect = screen.getByLabelText(/target bucket/i);
1526
1636
  await user_event.click(targetBucketSelect);
1527
1637
  await user_event.click(screen.getByRole('option', {
@@ -1619,7 +1729,9 @@ describe('BucketReplicationFormPage', ()=>{
1619
1729
  });
1620
1730
  renderBucketReplicationFormPage('test-bucket', 'no-priority');
1621
1731
  await waitFor(()=>{
1622
- const priorityInput = screen.getByLabelText(/rule priority/i);
1732
+ const priorityInput = screen.getByRole('spinbutton', {
1733
+ name: /rule priority/i
1734
+ });
1623
1735
  expect(priorityInput).toHaveValue(null);
1624
1736
  });
1625
1737
  });
@@ -1645,7 +1757,9 @@ describe('BucketReplicationFormPage', ()=>{
1645
1757
  });
1646
1758
  renderBucketReplicationFormPage('test-bucket', 'no-storage-class');
1647
1759
  await waitFor(()=>{
1648
- const storageClassSelect = screen.getByLabelText(/storage class/i);
1760
+ const storageClassSelect = screen.getByLabelText(/storage class/i, {
1761
+ selector: 'input'
1762
+ });
1649
1763
  expect(storageClassSelect).toBeInTheDocument();
1650
1764
  });
1651
1765
  });
@@ -1654,7 +1768,9 @@ describe('BucketReplicationFormPage', ()=>{
1654
1768
  it('accepts Rule ID with reasonable length', async ()=>{
1655
1769
  renderBucketReplicationFormPage();
1656
1770
  const longRuleId = 'rule-name-with-many-segments-' + 'segment-'.repeat(10);
1657
- const ruleIdInput = screen.getByLabelText(/rule id/i);
1771
+ const ruleIdInput = screen.getByRole('textbox', {
1772
+ name: /rule id/i
1773
+ });
1658
1774
  await user_event.type(ruleIdInput, longRuleId);
1659
1775
  await waitFor(()=>{
1660
1776
  expect(ruleIdInput).toHaveValue(longRuleId);
@@ -1663,7 +1779,9 @@ describe('BucketReplicationFormPage', ()=>{
1663
1779
  it('handles special characters in Rule ID', async ()=>{
1664
1780
  renderBucketReplicationFormPage();
1665
1781
  const validSpecialChars = 'rule-name_with.special-chars_123';
1666
- const ruleIdInput = screen.getByLabelText(/rule id/i);
1782
+ const ruleIdInput = screen.getByRole('textbox', {
1783
+ name: /rule id/i
1784
+ });
1667
1785
  await user_event.type(ruleIdInput, validSpecialChars);
1668
1786
  await user_event.tab();
1669
1787
  await waitFor(()=>{
@@ -1672,8 +1790,12 @@ describe('BucketReplicationFormPage', ()=>{
1672
1790
  });
1673
1791
  it('validates Priority with boundary values', async ()=>{
1674
1792
  renderBucketReplicationFormPage();
1675
- await user_event.type(screen.getByLabelText(/rule id/i), 'boundary-test');
1676
- const priorityInput = screen.getByLabelText(/rule priority/i);
1793
+ await user_event.type(screen.getByRole('textbox', {
1794
+ name: /rule id/i
1795
+ }), 'boundary-test');
1796
+ const priorityInput = screen.getByRole('spinbutton', {
1797
+ name: /rule priority/i
1798
+ });
1677
1799
  await user_event.clear(priorityInput);
1678
1800
  await user_event.type(priorityInput, '0');
1679
1801
  await user_event.tab();
@@ -1700,10 +1822,14 @@ describe('BucketReplicationFormPage', ()=>{
1700
1822
  name: 'Prefix filter'
1701
1823
  }));
1702
1824
  await waitFor(()=>{
1703
- expect(screen.getByLabelText(/prefix/i)).toBeInTheDocument();
1825
+ expect(screen.getByRole('textbox', {
1826
+ name: /prefix/i
1827
+ })).toBeInTheDocument();
1704
1828
  });
1705
1829
  const longPrefix = 'logs/' + 'a'.repeat(100);
1706
- const prefixInput = screen.getByLabelText(/prefix/i);
1830
+ const prefixInput = screen.getByRole('textbox', {
1831
+ name: /prefix/i
1832
+ });
1707
1833
  await user_event.type(prefixInput, longPrefix);
1708
1834
  await waitFor(()=>{
1709
1835
  expect(prefixInput).toHaveValue(longPrefix);
@@ -1711,7 +1837,9 @@ describe('BucketReplicationFormPage', ()=>{
1711
1837
  });
1712
1838
  it('accepts valid Role ARN format', async ()=>{
1713
1839
  renderBucketReplicationFormPage();
1714
- const roleArnInput = screen.getByLabelText(/role arn/i);
1840
+ const roleArnInput = screen.getByRole('textbox', {
1841
+ name: /role arn/i
1842
+ });
1715
1843
  await user_event.type(roleArnInput, 'arn:aws:iam::123456789012:role/replication-role');
1716
1844
  await user_event.tab();
1717
1845
  await waitFor(()=>{
@@ -1723,9 +1851,13 @@ describe('BucketReplicationFormPage', ()=>{
1723
1851
  const sameAccountToggle = findToggleByLabel('Same account destination');
1724
1852
  await user_event.click(sameAccountToggle);
1725
1853
  await waitFor(()=>{
1726
- expect(screen.getByLabelText(/target account id/i)).toBeInTheDocument();
1854
+ expect(screen.getByRole('textbox', {
1855
+ name: /target account id/i
1856
+ })).toBeInTheDocument();
1857
+ });
1858
+ const accountIdInput = screen.getByRole('textbox', {
1859
+ name: /target account id/i
1727
1860
  });
1728
- const accountIdInput = screen.getByLabelText(/target account id/i);
1729
1861
  await user_event.type(accountIdInput, '123456789012');
1730
1862
  await user_event.tab();
1731
1863
  await waitFor(()=>{
@@ -1744,9 +1876,13 @@ describe('BucketReplicationFormPage', ()=>{
1744
1876
  const encryptToggle = findToggleByLabel('Encrypt replicated objects');
1745
1877
  await user_event.click(encryptToggle);
1746
1878
  await waitFor(()=>{
1747
- expect(screen.getByLabelText(/replica kms key id/i)).toBeInTheDocument();
1879
+ expect(screen.getByRole('textbox', {
1880
+ name: /replica kms key id/i
1881
+ })).toBeInTheDocument();
1882
+ });
1883
+ const kmsKeyInput = screen.getByRole('textbox', {
1884
+ name: /replica kms key id/i
1748
1885
  });
1749
- const kmsKeyInput = screen.getByLabelText(/replica kms key id/i);
1750
1886
  const validKmsArn = 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012';
1751
1887
  await user_event.type(kmsKeyInput, validKmsArn);
1752
1888
  await waitFor(()=>{
@@ -1754,4 +1890,66 @@ describe('BucketReplicationFormPage', ()=>{
1754
1890
  });
1755
1891
  });
1756
1892
  });
1893
+ describe('ISV Bucket Warning', ()=>{
1894
+ const setupISVMock = (isvApplication = 'Veeam')=>{
1895
+ mockUseISVBucketStatus.mockReturnValue({
1896
+ isVeeamBucket: 'Veeam' === isvApplication,
1897
+ isCommvaultBucket: 'Commvault' === isvApplication,
1898
+ isKastenBucket: 'Kasten' === isvApplication,
1899
+ isISVManaged: true,
1900
+ isvApplication,
1901
+ isLoading: false,
1902
+ bucketTagsStatus: 'success'
1903
+ });
1904
+ };
1905
+ it('renders ISV warning banner and checkbox when bucket is ISV-managed', ()=>{
1906
+ setupISVMock('Veeam');
1907
+ renderBucketReplicationFormPage();
1908
+ expect(screen.getByText(/bucket used for external integration with Veeam/i)).toBeInTheDocument();
1909
+ expect(screen.getByText(/rendering the replicated data unusable for recovery/i)).toBeInTheDocument();
1910
+ expect(screen.getByLabelText(/i understand what i'm doing/i)).toBeInTheDocument();
1911
+ });
1912
+ it('does not render ISV warning for non-ISV buckets', ()=>{
1913
+ renderBucketReplicationFormPage();
1914
+ expect(screen.queryByText(/bucket used for external integration with/i)).not.toBeInTheDocument();
1915
+ expect(screen.queryByLabelText(/i understand what i'm doing/i)).not.toBeInTheDocument();
1916
+ });
1917
+ it('blocks form submission when ISV checkbox is unchecked', async ()=>{
1918
+ setupISVMock('Veeam');
1919
+ renderBucketReplicationFormPage();
1920
+ await fillRequiredFields({
1921
+ ruleId: 'isv-rule'
1922
+ });
1923
+ await waitFor(()=>{
1924
+ const createButton = screen.getByRole('button', {
1925
+ name: /create/i
1926
+ });
1927
+ expect(createButton).toBeDisabled();
1928
+ });
1929
+ });
1930
+ it('allows form submission when ISV checkbox is checked', async ()=>{
1931
+ setupISVMock('Veeam');
1932
+ renderBucketReplicationFormPage();
1933
+ await fillRequiredFields({
1934
+ ruleId: 'isv-rule'
1935
+ });
1936
+ const checkbox = screen.getByLabelText(/i understand what i'm doing/i);
1937
+ await user_event.click(checkbox);
1938
+ mockSuccessSubmit(mockMutate);
1939
+ await submitForm('create');
1940
+ await waitFor(()=>{
1941
+ expect(mockMutate).toHaveBeenCalled();
1942
+ });
1943
+ });
1944
+ it('displays correct application name for Commvault ISV bucket', ()=>{
1945
+ setupISVMock('Commvault');
1946
+ renderBucketReplicationFormPage();
1947
+ expect(screen.getByText(/bucket used for external integration with Commvault/i)).toBeInTheDocument();
1948
+ });
1949
+ it('displays correct application name for Kasten ISV bucket', ()=>{
1950
+ setupISVMock('Kasten');
1951
+ renderBucketReplicationFormPage();
1952
+ expect(screen.getByText(/bucket used for external integration with Kasten/i)).toBeInTheDocument();
1953
+ });
1954
+ });
1757
1955
  });