@scality/data-browser-library 1.0.0-preview.7 → 1.0.0-preview.9
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/dist/components/__tests__/BucketCreate.test.d.ts +1 -0
- package/dist/components/__tests__/BucketCreate.test.js +408 -0
- package/dist/components/__tests__/BucketLifecycleFormPage.test.d.ts +1 -0
- package/dist/components/__tests__/BucketLifecycleFormPage.test.js +618 -0
- package/dist/components/__tests__/BucketLifecycleList.test.d.ts +1 -0
- package/dist/components/__tests__/BucketLifecycleList.test.js +325 -0
- package/dist/components/__tests__/BucketList.test.js +190 -0
- package/dist/components/__tests__/BucketOverview.test.js +298 -8
- package/dist/components/__tests__/BucketReplicationFormPage.test.d.ts +1 -0
- package/dist/components/__tests__/BucketReplicationFormPage.test.js +1757 -0
- package/dist/components/__tests__/BucketReplicationList.test.d.ts +1 -0
- package/dist/components/__tests__/BucketReplicationList.test.js +344 -0
- package/dist/components/__tests__/DeleteBucketConfigRuleButton.test.d.ts +1 -0
- package/dist/components/__tests__/DeleteBucketConfigRuleButton.test.js +196 -0
- package/dist/components/__tests__/EmptyBucketButton.test.d.ts +1 -0
- package/dist/components/__tests__/EmptyBucketButton.test.js +302 -0
- package/dist/components/buckets/BucketCreate.d.ts +49 -0
- package/dist/components/buckets/BucketCreate.js +237 -0
- package/dist/components/buckets/BucketDetails.js +62 -10
- package/dist/components/buckets/BucketLifecycleFormPage.d.ts +15 -0
- package/dist/components/buckets/BucketLifecycleFormPage.js +1070 -0
- package/dist/components/buckets/BucketLifecycleList.d.ts +10 -0
- package/dist/components/buckets/BucketLifecycleList.js +270 -0
- package/dist/components/buckets/BucketList.d.ts +5 -2
- package/dist/components/buckets/BucketList.js +38 -28
- package/dist/components/buckets/BucketOverview.d.ts +65 -4
- package/dist/components/buckets/BucketOverview.js +261 -179
- package/dist/components/buckets/BucketPage.js +1 -1
- package/dist/components/buckets/BucketReplicationFormPage.d.ts +1 -0
- package/dist/components/buckets/BucketReplicationFormPage.js +834 -0
- package/dist/components/buckets/BucketReplicationList.d.ts +11 -0
- package/dist/components/buckets/BucketReplicationList.js +189 -0
- package/dist/components/buckets/DeleteBucketConfigRuleButton.d.ts +18 -0
- package/dist/components/buckets/DeleteBucketConfigRuleButton.js +53 -0
- package/dist/components/buckets/EmptyBucketButton.d.ts +5 -0
- package/dist/components/buckets/EmptyBucketButton.js +232 -0
- package/dist/components/buckets/EmptyBucketSummary.d.ts +9 -0
- package/dist/components/buckets/EmptyBucketSummary.js +60 -0
- package/dist/components/buckets/EmptyBucketSummaryList.d.ts +13 -0
- package/dist/components/buckets/EmptyBucketSummaryList.js +140 -0
- package/dist/components/buckets/notifications/BucketNotificationCreatePage.js +8 -8
- package/dist/components/index.d.ts +8 -1
- package/dist/components/index.js +9 -2
- package/dist/components/objects/ObjectLock/EditRetentionButton.d.ts +4 -0
- package/dist/components/objects/ObjectLock/EditRetentionButton.js +32 -0
- package/dist/components/objects/ObjectLock/ObjectLockRetentionSettings.d.ts +3 -0
- package/dist/components/objects/ObjectLock/ObjectLockRetentionSettings.js +211 -0
- package/dist/components/objects/ObjectLock/ObjectLockSettings.d.ts +9 -0
- package/dist/components/objects/ObjectLock/ObjectLockSettings.js +158 -0
- package/dist/components/objects/ObjectLock/ObjectLockSettingsUtils.d.ts +8 -0
- package/dist/components/objects/ObjectLock/ObjectLockSettingsUtils.js +39 -0
- package/dist/components/objects/ObjectLock/__tests__/EditRetentionButton.test.d.ts +1 -0
- package/dist/components/objects/ObjectLock/__tests__/EditRetentionButton.test.js +204 -0
- package/dist/components/objects/ObjectLock/__tests__/ObjectLockSettings.test.d.ts +1 -0
- package/dist/components/objects/ObjectLock/__tests__/ObjectLockSettings.test.js +374 -0
- package/dist/components/ui/ArrayFieldActions.d.ts +36 -0
- package/dist/components/ui/ArrayFieldActions.js +38 -0
- package/dist/components/ui/ConfirmDeleteRuleModal.d.ts +16 -0
- package/dist/components/ui/ConfirmDeleteRuleModal.js +43 -0
- package/dist/components/ui/FilterFormSection.d.ts +44 -0
- package/dist/components/ui/FilterFormSection.js +159 -0
- package/dist/config/factory.d.ts +13 -2
- package/dist/config/factory.js +9 -6
- package/dist/hooks/__tests__/useISVBucketDetection.test.d.ts +1 -0
- package/dist/hooks/__tests__/useISVBucketDetection.test.js +188 -0
- package/dist/hooks/factories/__tests__/useCreateS3QueryHook.test.js +44 -1
- package/dist/hooks/factories/useCreateS3QueryHook.js +22 -1
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +5 -1
- package/dist/hooks/useDeleteBucketConfigRule.d.ts +26 -0
- package/dist/hooks/useDeleteBucketConfigRule.js +46 -0
- package/dist/hooks/useEmptyBucket.d.ts +27 -0
- package/dist/hooks/useEmptyBucket.js +116 -0
- package/dist/hooks/useISVBucketDetection.d.ts +15 -0
- package/dist/hooks/useISVBucketDetection.js +27 -0
- package/dist/hooks/useTableRowSelection.d.ts +9 -0
- package/dist/hooks/useTableRowSelection.js +45 -0
- package/dist/test/setup.js +8 -0
- package/dist/test/testUtils.d.ts +99 -17
- package/dist/test/testUtils.js +64 -16
- package/dist/test/utils/errorHandling.test.js +39 -1
- package/dist/utils/constants.d.ts +12 -0
- package/dist/utils/constants.js +9 -0
- package/dist/utils/errorHandling.d.ts +9 -0
- package/dist/utils/errorHandling.js +6 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/s3RuleUtils.d.ts +53 -0
- package/dist/utils/s3RuleUtils.js +101 -0
- package/package.json +1 -1
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import user_event from "@testing-library/user-event";
|
|
4
|
+
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
|
5
|
+
import { createTestWrapper, findToggleByLabel, mockErrorSubmit, mockOffsetSize, mockSuccessSubmit, submitForm } from "../../test/testUtils.js";
|
|
6
|
+
import { BucketLifecycleFormPage } from "../buckets/BucketLifecycleFormPage.js";
|
|
7
|
+
import { useGetBucketLifecycle, useSetBucketLifecycle } from "../../hooks/bucketConfiguration.js";
|
|
8
|
+
jest.mock("../../hooks/bucketConfiguration", ()=>({
|
|
9
|
+
useGetBucketLifecycle: jest.fn(),
|
|
10
|
+
useSetBucketLifecycle: jest.fn()
|
|
11
|
+
}));
|
|
12
|
+
const mockUseGetBucketLifecycle = jest.mocked(useGetBucketLifecycle);
|
|
13
|
+
const mockUseSetBucketLifecycle = jest.mocked(useSetBucketLifecycle);
|
|
14
|
+
const mockNavigate = jest.fn();
|
|
15
|
+
const mockShowToast = jest.fn();
|
|
16
|
+
jest.mock("react-router-dom", ()=>({
|
|
17
|
+
...jest.requireActual("react-router-dom"),
|
|
18
|
+
useNavigate: ()=>mockNavigate
|
|
19
|
+
}));
|
|
20
|
+
jest.mock("@scality/core-ui", ()=>({
|
|
21
|
+
...jest.requireActual("@scality/core-ui"),
|
|
22
|
+
useToast: ()=>({
|
|
23
|
+
showToast: mockShowToast
|
|
24
|
+
})
|
|
25
|
+
}));
|
|
26
|
+
const renderBucketLifecycleFormPage = (bucketName = "test-bucket", ruleId)=>{
|
|
27
|
+
const Wrapper = createTestWrapper();
|
|
28
|
+
const path = ruleId ? `/buckets/${bucketName}/lifecycle/${ruleId}/edit` : `/buckets/${bucketName}/lifecycle/create`;
|
|
29
|
+
return render(/*#__PURE__*/ jsx(MemoryRouter, {
|
|
30
|
+
initialEntries: [
|
|
31
|
+
path
|
|
32
|
+
],
|
|
33
|
+
children: /*#__PURE__*/ jsx(Wrapper, {
|
|
34
|
+
children: /*#__PURE__*/ jsxs(Routes, {
|
|
35
|
+
children: [
|
|
36
|
+
/*#__PURE__*/ jsx(Route, {
|
|
37
|
+
path: "/buckets/:bucketName/lifecycle/create",
|
|
38
|
+
element: /*#__PURE__*/ jsx(BucketLifecycleFormPage, {})
|
|
39
|
+
}),
|
|
40
|
+
/*#__PURE__*/ jsx(Route, {
|
|
41
|
+
path: "/buckets/:bucketName/lifecycle/:ruleId/edit",
|
|
42
|
+
element: /*#__PURE__*/ jsx(BucketLifecycleFormPage, {})
|
|
43
|
+
})
|
|
44
|
+
]
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
}));
|
|
48
|
+
};
|
|
49
|
+
describe("BucketLifecycleFormPage", ()=>{
|
|
50
|
+
const mockMutate = jest.fn();
|
|
51
|
+
const enableExpirationAction = async ()=>{
|
|
52
|
+
const expirationToggle = findToggleByLabel("Expiration current version");
|
|
53
|
+
await user_event.click(expirationToggle);
|
|
54
|
+
};
|
|
55
|
+
const fillRequiredFields = async (ruleId)=>{
|
|
56
|
+
await user_event.type(screen.getByLabelText(/rule id/i), ruleId);
|
|
57
|
+
await enableExpirationAction();
|
|
58
|
+
};
|
|
59
|
+
beforeEach(()=>{
|
|
60
|
+
jest.clearAllMocks();
|
|
61
|
+
mockNavigate.mockClear();
|
|
62
|
+
mockShowToast.mockClear();
|
|
63
|
+
mockOffsetSize(800, 600);
|
|
64
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
65
|
+
data: void 0,
|
|
66
|
+
status: "success"
|
|
67
|
+
});
|
|
68
|
+
mockUseSetBucketLifecycle.mockReturnValue({
|
|
69
|
+
mutate: mockMutate,
|
|
70
|
+
isPending: false
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("Page Rendering", ()=>{
|
|
74
|
+
it("renders create mode with correct title", ()=>{
|
|
75
|
+
renderBucketLifecycleFormPage();
|
|
76
|
+
expect(screen.getByText("Create Lifecycle Rule")).toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
it("renders edit mode with correct title when rule exists", async ()=>{
|
|
79
|
+
const existingRule = {
|
|
80
|
+
ID: "test-rule",
|
|
81
|
+
Status: "Enabled",
|
|
82
|
+
Filter: {},
|
|
83
|
+
Expiration: {
|
|
84
|
+
Days: 30
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
88
|
+
data: {
|
|
89
|
+
Rules: [
|
|
90
|
+
existingRule
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
status: "success"
|
|
94
|
+
});
|
|
95
|
+
renderBucketLifecycleFormPage("test-bucket", "test-rule");
|
|
96
|
+
await waitFor(()=>{
|
|
97
|
+
expect(screen.getByText("Edit Lifecycle Rule")).toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
it("shows loading state when fetching lifecycle data", ()=>{
|
|
101
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
102
|
+
data: void 0,
|
|
103
|
+
status: "pending"
|
|
104
|
+
});
|
|
105
|
+
renderBucketLifecycleFormPage();
|
|
106
|
+
expect(screen.getByText("Loading...")).toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
it("shows error when rule not found in edit mode", ()=>{
|
|
109
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
110
|
+
data: {
|
|
111
|
+
Rules: []
|
|
112
|
+
},
|
|
113
|
+
status: "success"
|
|
114
|
+
});
|
|
115
|
+
renderBucketLifecycleFormPage("test-bucket", "non-existent");
|
|
116
|
+
expect(screen.getByText("Rule not found")).toBeInTheDocument();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe("Form Fields - Initial State", ()=>{
|
|
120
|
+
it("renders required form fields in create mode", ()=>{
|
|
121
|
+
renderBucketLifecycleFormPage();
|
|
122
|
+
expect(screen.getByLabelText(/rule id/i)).toBeInTheDocument();
|
|
123
|
+
expect(screen.getByLabelText(/status/i)).toBeInTheDocument();
|
|
124
|
+
expect(screen.getByLabelText(/filter/i)).toBeInTheDocument();
|
|
125
|
+
});
|
|
126
|
+
it("renders all lifecycle action toggles", ()=>{
|
|
127
|
+
renderBucketLifecycleFormPage();
|
|
128
|
+
expect(screen.getByText("Transition current version")).toBeInTheDocument();
|
|
129
|
+
expect(screen.getByText("Expiration current version")).toBeInTheDocument();
|
|
130
|
+
expect(screen.getByText("Transition noncurrent version")).toBeInTheDocument();
|
|
131
|
+
expect(screen.getByText("Expiration noncurrent version")).toBeInTheDocument();
|
|
132
|
+
expect(screen.getByText("Expire incomplete multipart upload")).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
it("shows all filter type options when filter select is opened", async ()=>{
|
|
135
|
+
renderBucketLifecycleFormPage();
|
|
136
|
+
const filterSelect = screen.getByLabelText(/filter/i);
|
|
137
|
+
await user_event.click(filterSelect);
|
|
138
|
+
expect(screen.getByRole("option", {
|
|
139
|
+
name: "All objects"
|
|
140
|
+
})).toBeInTheDocument();
|
|
141
|
+
expect(screen.getByRole("option", {
|
|
142
|
+
name: "Prefix filter"
|
|
143
|
+
})).toBeInTheDocument();
|
|
144
|
+
expect(screen.getByRole("option", {
|
|
145
|
+
name: "Tags filter"
|
|
146
|
+
})).toBeInTheDocument();
|
|
147
|
+
expect(screen.getByRole("option", {
|
|
148
|
+
name: "Prefix and tags filter"
|
|
149
|
+
})).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
it("disables rule ID input in edit mode", async ()=>{
|
|
152
|
+
const existingRule = {
|
|
153
|
+
ID: "test-rule",
|
|
154
|
+
Status: "Enabled",
|
|
155
|
+
Filter: {},
|
|
156
|
+
Expiration: {
|
|
157
|
+
Days: 30
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
161
|
+
data: {
|
|
162
|
+
Rules: [
|
|
163
|
+
existingRule
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
status: "success"
|
|
167
|
+
});
|
|
168
|
+
renderBucketLifecycleFormPage("test-bucket", "test-rule");
|
|
169
|
+
await waitFor(()=>{
|
|
170
|
+
expect(screen.getByText("test-rule")).toBeInTheDocument();
|
|
171
|
+
});
|
|
172
|
+
expect(screen.queryByRole("textbox", {
|
|
173
|
+
name: /rule id/i
|
|
174
|
+
})).not.toBeInTheDocument();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe("Form Validation", ()=>{
|
|
178
|
+
it("validates rule ID is required", async ()=>{
|
|
179
|
+
renderBucketLifecycleFormPage();
|
|
180
|
+
const ruleIdInput = screen.getByLabelText(/rule id/i);
|
|
181
|
+
await user_event.type(ruleIdInput, "test");
|
|
182
|
+
await user_event.clear(ruleIdInput);
|
|
183
|
+
await user_event.tab();
|
|
184
|
+
await waitFor(()=>{
|
|
185
|
+
expect(screen.getByText(/rule id is required/i)).toBeInTheDocument();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
it("validates rule ID uniqueness against existing rules", async ()=>{
|
|
189
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
190
|
+
data: {
|
|
191
|
+
Rules: [
|
|
192
|
+
{
|
|
193
|
+
ID: "existing-rule",
|
|
194
|
+
Status: "Enabled",
|
|
195
|
+
Expiration: {
|
|
196
|
+
Days: 30
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
},
|
|
201
|
+
status: "success"
|
|
202
|
+
});
|
|
203
|
+
renderBucketLifecycleFormPage();
|
|
204
|
+
const ruleIdInput = screen.getByLabelText(/rule id/i);
|
|
205
|
+
await user_event.type(ruleIdInput, "existing-rule");
|
|
206
|
+
await user_event.tab();
|
|
207
|
+
await waitFor(()=>{
|
|
208
|
+
expect(screen.getByText(/a rule with this id already exists/i)).toBeInTheDocument();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
it("disables create button when form is pristine", ()=>{
|
|
212
|
+
renderBucketLifecycleFormPage();
|
|
213
|
+
const createButton = screen.getByRole("button", {
|
|
214
|
+
name: /create/i
|
|
215
|
+
});
|
|
216
|
+
expect(createButton).toBeDisabled();
|
|
217
|
+
});
|
|
218
|
+
it("disables create button when no lifecycle action is enabled", async ()=>{
|
|
219
|
+
renderBucketLifecycleFormPage();
|
|
220
|
+
await user_event.type(screen.getByLabelText(/rule id/i), "test-rule");
|
|
221
|
+
const createButton = screen.getByRole("button", {
|
|
222
|
+
name: /create/i
|
|
223
|
+
});
|
|
224
|
+
expect(createButton).toBeDisabled();
|
|
225
|
+
});
|
|
226
|
+
it("enables create button when form is valid and has at least one action", async ()=>{
|
|
227
|
+
renderBucketLifecycleFormPage();
|
|
228
|
+
await fillRequiredFields("valid-rule");
|
|
229
|
+
await waitFor(()=>{
|
|
230
|
+
const createButton = screen.getByRole("button", {
|
|
231
|
+
name: /create/i
|
|
232
|
+
});
|
|
233
|
+
expect(createButton).toBeEnabled();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
describe("Form Submission - Create Mode", ()=>{
|
|
238
|
+
it("submits minimal lifecycle rule with expiration action", async ()=>{
|
|
239
|
+
renderBucketLifecycleFormPage();
|
|
240
|
+
await fillRequiredFields("minimal-rule");
|
|
241
|
+
mockSuccessSubmit(mockMutate);
|
|
242
|
+
await submitForm("create");
|
|
243
|
+
await waitFor(()=>{
|
|
244
|
+
expect(mockMutate).toHaveBeenCalledWith(expect.objectContaining({
|
|
245
|
+
Bucket: "test-bucket",
|
|
246
|
+
LifecycleConfiguration: {
|
|
247
|
+
Rules: expect.arrayContaining([
|
|
248
|
+
expect.objectContaining({
|
|
249
|
+
ID: "minimal-rule",
|
|
250
|
+
Status: "Enabled",
|
|
251
|
+
Filter: {},
|
|
252
|
+
Expiration: {
|
|
253
|
+
Days: 30
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
])
|
|
257
|
+
}
|
|
258
|
+
}), expect.any(Object));
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
it("navigates to bucket lifecycle tab on successful creation", async ()=>{
|
|
262
|
+
renderBucketLifecycleFormPage();
|
|
263
|
+
await fillRequiredFields("success-rule");
|
|
264
|
+
mockSuccessSubmit(mockMutate);
|
|
265
|
+
await submitForm("create");
|
|
266
|
+
await waitFor(()=>{
|
|
267
|
+
expect(mockNavigate).toHaveBeenCalledWith("/buckets/test-bucket?tab=lifecycle");
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
it("displays success toast after rule creation", async ()=>{
|
|
271
|
+
renderBucketLifecycleFormPage();
|
|
272
|
+
await fillRequiredFields("toast-rule");
|
|
273
|
+
mockSuccessSubmit(mockMutate);
|
|
274
|
+
await submitForm("create");
|
|
275
|
+
await waitFor(()=>{
|
|
276
|
+
expect(mockShowToast).toHaveBeenCalledWith({
|
|
277
|
+
open: true,
|
|
278
|
+
message: "Lifecycle rule created successfully",
|
|
279
|
+
status: "success"
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
it("displays error toast when creation fails", async ()=>{
|
|
284
|
+
renderBucketLifecycleFormPage();
|
|
285
|
+
await fillRequiredFields("error-rule");
|
|
286
|
+
mockErrorSubmit(mockMutate, "Access Denied");
|
|
287
|
+
await submitForm("create");
|
|
288
|
+
await waitFor(()=>{
|
|
289
|
+
expect(mockShowToast).toHaveBeenCalledWith({
|
|
290
|
+
open: true,
|
|
291
|
+
message: "Access Denied",
|
|
292
|
+
status: "error"
|
|
293
|
+
});
|
|
294
|
+
expect(mockNavigate).not.toHaveBeenCalled();
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
it("appends new rule to existing rules on creation", async ()=>{
|
|
298
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
299
|
+
data: {
|
|
300
|
+
Rules: [
|
|
301
|
+
{
|
|
302
|
+
ID: "existing-rule",
|
|
303
|
+
Status: "Enabled",
|
|
304
|
+
Filter: {},
|
|
305
|
+
Expiration: {
|
|
306
|
+
Days: 90
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
status: "success"
|
|
312
|
+
});
|
|
313
|
+
renderBucketLifecycleFormPage();
|
|
314
|
+
await fillRequiredFields("new-rule");
|
|
315
|
+
mockSuccessSubmit(mockMutate);
|
|
316
|
+
await submitForm("create");
|
|
317
|
+
await waitFor(()=>{
|
|
318
|
+
const call = mockMutate.mock.calls[0][0];
|
|
319
|
+
expect(call.LifecycleConfiguration.Rules).toHaveLength(2);
|
|
320
|
+
expect(call.LifecycleConfiguration.Rules[0].ID).toBe("existing-rule");
|
|
321
|
+
expect(call.LifecycleConfiguration.Rules[1].ID).toBe("new-rule");
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
describe("Form Submission - Edit Mode", ()=>{
|
|
326
|
+
it("updates existing rule in edit mode", async ()=>{
|
|
327
|
+
const existingRule = {
|
|
328
|
+
ID: "update-rule",
|
|
329
|
+
Status: "Enabled",
|
|
330
|
+
Filter: {},
|
|
331
|
+
Expiration: {
|
|
332
|
+
Days: 30
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
336
|
+
data: {
|
|
337
|
+
Rules: [
|
|
338
|
+
existingRule
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
status: "success"
|
|
342
|
+
});
|
|
343
|
+
renderBucketLifecycleFormPage("test-bucket", "update-rule");
|
|
344
|
+
await waitFor(()=>{
|
|
345
|
+
expect(screen.getByText("Edit Lifecycle Rule")).toBeInTheDocument();
|
|
346
|
+
});
|
|
347
|
+
const statusSelect = screen.getByLabelText(/status/i);
|
|
348
|
+
await user_event.click(statusSelect);
|
|
349
|
+
await user_event.click(screen.getByRole("option", {
|
|
350
|
+
name: "Disabled"
|
|
351
|
+
}));
|
|
352
|
+
mockSuccessSubmit(mockMutate);
|
|
353
|
+
await submitForm("save");
|
|
354
|
+
await waitFor(()=>{
|
|
355
|
+
const call = mockMutate.mock.calls[0][0];
|
|
356
|
+
expect(call.LifecycleConfiguration.Rules).toHaveLength(1);
|
|
357
|
+
expect(call.LifecycleConfiguration.Rules[0].ID).toBe("update-rule");
|
|
358
|
+
expect(call.LifecycleConfiguration.Rules[0].Status).toBe("Disabled");
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
it("displays update success toast in edit mode", async ()=>{
|
|
362
|
+
const existingRule = {
|
|
363
|
+
ID: "edit-rule",
|
|
364
|
+
Status: "Enabled",
|
|
365
|
+
Filter: {},
|
|
366
|
+
Expiration: {
|
|
367
|
+
Days: 30
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
371
|
+
data: {
|
|
372
|
+
Rules: [
|
|
373
|
+
existingRule
|
|
374
|
+
]
|
|
375
|
+
},
|
|
376
|
+
status: "success"
|
|
377
|
+
});
|
|
378
|
+
renderBucketLifecycleFormPage("test-bucket", "edit-rule");
|
|
379
|
+
await waitFor(()=>{
|
|
380
|
+
expect(screen.getByText("Edit Lifecycle Rule")).toBeInTheDocument();
|
|
381
|
+
});
|
|
382
|
+
const statusSelect = screen.getByLabelText(/status/i);
|
|
383
|
+
await user_event.click(statusSelect);
|
|
384
|
+
await user_event.click(screen.getByRole("option", {
|
|
385
|
+
name: "Disabled"
|
|
386
|
+
}));
|
|
387
|
+
mockSuccessSubmit(mockMutate);
|
|
388
|
+
await submitForm("save");
|
|
389
|
+
await waitFor(()=>{
|
|
390
|
+
expect(mockShowToast).toHaveBeenCalledWith({
|
|
391
|
+
open: true,
|
|
392
|
+
message: "Lifecycle rule updated successfully",
|
|
393
|
+
status: "success"
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
it("disables save button when form is not dirty", async ()=>{
|
|
398
|
+
const existingRule = {
|
|
399
|
+
ID: "unchanged-rule",
|
|
400
|
+
Status: "Enabled",
|
|
401
|
+
Filter: {},
|
|
402
|
+
Expiration: {
|
|
403
|
+
Days: 30
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
407
|
+
data: {
|
|
408
|
+
Rules: [
|
|
409
|
+
existingRule
|
|
410
|
+
]
|
|
411
|
+
},
|
|
412
|
+
status: "success"
|
|
413
|
+
});
|
|
414
|
+
renderBucketLifecycleFormPage("test-bucket", "unchanged-rule");
|
|
415
|
+
await waitFor(()=>{
|
|
416
|
+
expect(screen.getByText("Edit Lifecycle Rule")).toBeInTheDocument();
|
|
417
|
+
});
|
|
418
|
+
const saveButton = screen.getByRole("button", {
|
|
419
|
+
name: /save/i
|
|
420
|
+
});
|
|
421
|
+
expect(saveButton).toBeDisabled();
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
describe("Rule Data Loading", ()=>{
|
|
425
|
+
it("loads rule with prefix filter correctly", async ()=>{
|
|
426
|
+
const rule = {
|
|
427
|
+
ID: "prefix-rule",
|
|
428
|
+
Status: "Enabled",
|
|
429
|
+
Filter: {
|
|
430
|
+
Prefix: "documents/"
|
|
431
|
+
},
|
|
432
|
+
Expiration: {
|
|
433
|
+
Days: 90
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
437
|
+
data: {
|
|
438
|
+
Rules: [
|
|
439
|
+
rule
|
|
440
|
+
]
|
|
441
|
+
},
|
|
442
|
+
status: "success"
|
|
443
|
+
});
|
|
444
|
+
renderBucketLifecycleFormPage("test-bucket", "prefix-rule");
|
|
445
|
+
await waitFor(()=>{
|
|
446
|
+
const prefixInput = screen.getByLabelText(/prefix/i);
|
|
447
|
+
expect(prefixInput).toHaveValue("documents/");
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
it("loads rule with single tag filter correctly", async ()=>{
|
|
451
|
+
const rule = {
|
|
452
|
+
ID: "tag-rule",
|
|
453
|
+
Status: "Enabled",
|
|
454
|
+
Filter: {
|
|
455
|
+
Tag: {
|
|
456
|
+
Key: "env",
|
|
457
|
+
Value: "prod"
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
Expiration: {
|
|
461
|
+
Days: 180
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
465
|
+
data: {
|
|
466
|
+
Rules: [
|
|
467
|
+
rule
|
|
468
|
+
]
|
|
469
|
+
},
|
|
470
|
+
status: "success"
|
|
471
|
+
});
|
|
472
|
+
renderBucketLifecycleFormPage("test-bucket", "tag-rule");
|
|
473
|
+
await waitFor(()=>{
|
|
474
|
+
expect(screen.getByDisplayValue("env")).toBeInTheDocument();
|
|
475
|
+
expect(screen.getByDisplayValue("prod")).toBeInTheDocument();
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
it("loads rule with expiration days correctly", async ()=>{
|
|
479
|
+
const rule = {
|
|
480
|
+
ID: "expiration-rule",
|
|
481
|
+
Status: "Enabled",
|
|
482
|
+
Filter: {},
|
|
483
|
+
Expiration: {
|
|
484
|
+
Days: 365
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
488
|
+
data: {
|
|
489
|
+
Rules: [
|
|
490
|
+
rule
|
|
491
|
+
]
|
|
492
|
+
},
|
|
493
|
+
status: "success"
|
|
494
|
+
});
|
|
495
|
+
renderBucketLifecycleFormPage("test-bucket", "expiration-rule");
|
|
496
|
+
await waitFor(()=>{
|
|
497
|
+
const expirationToggle = findToggleByLabel("Expiration current version");
|
|
498
|
+
expect(expirationToggle).toBeChecked();
|
|
499
|
+
const daysInput = screen.getByLabelText(/^days$/i);
|
|
500
|
+
expect(daysInput).toHaveValue(365);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
it("loads rule with noncurrent version expiration correctly", async ()=>{
|
|
504
|
+
const rule = {
|
|
505
|
+
ID: "noncurrent-rule",
|
|
506
|
+
Status: "Enabled",
|
|
507
|
+
Filter: {},
|
|
508
|
+
NoncurrentVersionExpiration: {
|
|
509
|
+
NoncurrentDays: 90,
|
|
510
|
+
NewerNoncurrentVersions: 3
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
514
|
+
data: {
|
|
515
|
+
Rules: [
|
|
516
|
+
rule
|
|
517
|
+
]
|
|
518
|
+
},
|
|
519
|
+
status: "success"
|
|
520
|
+
});
|
|
521
|
+
renderBucketLifecycleFormPage("test-bucket", "noncurrent-rule");
|
|
522
|
+
await waitFor(()=>{
|
|
523
|
+
const noncurrentExpirationToggle = findToggleByLabel("Expiration noncurrent version");
|
|
524
|
+
expect(noncurrentExpirationToggle).toBeChecked();
|
|
525
|
+
const noncurrentDaysInput = screen.getByLabelText(/noncurrent days/i);
|
|
526
|
+
expect(noncurrentDaysInput).toHaveValue(90);
|
|
527
|
+
const keepVersionsInput = screen.getByLabelText(/keep newer versions/i);
|
|
528
|
+
expect(keepVersionsInput).toHaveValue(3);
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
it("loads rule with abort incomplete multipart upload correctly", async ()=>{
|
|
532
|
+
const rule = {
|
|
533
|
+
ID: "mpu-rule",
|
|
534
|
+
Status: "Enabled",
|
|
535
|
+
Filter: {},
|
|
536
|
+
AbortIncompleteMultipartUpload: {
|
|
537
|
+
DaysAfterInitiation: 7
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
541
|
+
data: {
|
|
542
|
+
Rules: [
|
|
543
|
+
rule
|
|
544
|
+
]
|
|
545
|
+
},
|
|
546
|
+
status: "success"
|
|
547
|
+
});
|
|
548
|
+
renderBucketLifecycleFormPage("test-bucket", "mpu-rule");
|
|
549
|
+
await waitFor(()=>{
|
|
550
|
+
const mpuToggle = findToggleByLabel("Expire incomplete multipart upload");
|
|
551
|
+
expect(mpuToggle).toBeChecked();
|
|
552
|
+
const mpuDaysInput = screen.getByLabelText(/days after initiation/i);
|
|
553
|
+
expect(mpuDaysInput).toHaveValue(7);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
it("loads rule with transition correctly", async ()=>{
|
|
557
|
+
const rule = {
|
|
558
|
+
ID: "transition-rule",
|
|
559
|
+
Status: "Enabled",
|
|
560
|
+
Filter: {},
|
|
561
|
+
Transitions: [
|
|
562
|
+
{
|
|
563
|
+
Days: 30,
|
|
564
|
+
StorageClass: "GLACIER"
|
|
565
|
+
}
|
|
566
|
+
]
|
|
567
|
+
};
|
|
568
|
+
mockUseGetBucketLifecycle.mockReturnValue({
|
|
569
|
+
data: {
|
|
570
|
+
Rules: [
|
|
571
|
+
rule
|
|
572
|
+
]
|
|
573
|
+
},
|
|
574
|
+
status: "success"
|
|
575
|
+
});
|
|
576
|
+
renderBucketLifecycleFormPage("test-bucket", "transition-rule");
|
|
577
|
+
await waitFor(()=>{
|
|
578
|
+
const transitionToggle = findToggleByLabel("Transition current version");
|
|
579
|
+
expect(transitionToggle).toBeChecked();
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
describe("Navigation", ()=>{
|
|
584
|
+
it("navigates back to bucket lifecycle tab when cancel is clicked", ()=>{
|
|
585
|
+
renderBucketLifecycleFormPage();
|
|
586
|
+
const cancelButton = screen.getByRole("button", {
|
|
587
|
+
name: /cancel/i
|
|
588
|
+
});
|
|
589
|
+
fireEvent.click(cancelButton);
|
|
590
|
+
expect(mockNavigate).toHaveBeenCalledWith("/buckets/test-bucket?tab=lifecycle");
|
|
591
|
+
});
|
|
592
|
+
it("disables cancel button when mutation is pending", ()=>{
|
|
593
|
+
mockUseSetBucketLifecycle.mockReturnValue({
|
|
594
|
+
mutate: mockMutate,
|
|
595
|
+
isPending: true
|
|
596
|
+
});
|
|
597
|
+
renderBucketLifecycleFormPage();
|
|
598
|
+
const cancelButton = screen.getByRole("button", {
|
|
599
|
+
name: /cancel/i
|
|
600
|
+
});
|
|
601
|
+
expect(cancelButton).toBeDisabled();
|
|
602
|
+
});
|
|
603
|
+
it("disables submit button when mutation is pending", async ()=>{
|
|
604
|
+
mockUseSetBucketLifecycle.mockReturnValue({
|
|
605
|
+
mutate: mockMutate,
|
|
606
|
+
isPending: true
|
|
607
|
+
});
|
|
608
|
+
renderBucketLifecycleFormPage();
|
|
609
|
+
await fillRequiredFields("test-rule");
|
|
610
|
+
await waitFor(()=>{
|
|
611
|
+
const createButton = screen.getByRole("button", {
|
|
612
|
+
name: /create/i
|
|
613
|
+
});
|
|
614
|
+
expect(createButton).toBeDisabled();
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|