@scality/data-browser-library 1.0.0-preview.2
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/Editor.d.ts +12 -0
- package/dist/components/Editor.js +28 -0
- package/dist/components/__tests__/BucketList.test.d.ts +1 -0
- package/dist/components/__tests__/BucketList.test.js +225 -0
- package/dist/components/__tests__/BucketOverview.test.d.ts +1 -0
- package/dist/components/__tests__/BucketOverview.test.js +479 -0
- package/dist/components/__tests__/BucketPolicyPage.test.d.ts +1 -0
- package/dist/components/__tests__/BucketPolicyPage.test.js +213 -0
- package/dist/components/__tests__/CreateFolderButton.test.d.ts +1 -0
- package/dist/components/__tests__/CreateFolderButton.test.js +147 -0
- package/dist/components/__tests__/DeleteBucketButton.test.d.ts +1 -0
- package/dist/components/__tests__/DeleteBucketButton.test.js +272 -0
- package/dist/components/__tests__/DeleteObjectButton.test.d.ts +1 -0
- package/dist/components/__tests__/DeleteObjectButton.test.js +302 -0
- package/dist/components/__tests__/MetadataSearch.test.d.ts +1 -0
- package/dist/components/__tests__/MetadataSearch.test.js +201 -0
- package/dist/components/__tests__/ObjectList.test.d.ts +1 -0
- package/dist/components/__tests__/ObjectList.test.js +283 -0
- package/dist/components/__tests__/UploadButton.test.d.ts +1 -0
- package/dist/components/__tests__/UploadButton.test.js +144 -0
- package/dist/components/buckets/BucketDetails.d.ts +1 -0
- package/dist/components/buckets/BucketDetails.js +51 -0
- package/dist/components/buckets/BucketList.d.ts +12 -0
- package/dist/components/buckets/BucketList.js +136 -0
- package/dist/components/buckets/BucketLocation.d.ts +3 -0
- package/dist/components/buckets/BucketLocation.js +16 -0
- package/dist/components/buckets/BucketOverview.d.ts +14 -0
- package/dist/components/buckets/BucketOverview.js +209 -0
- package/dist/components/buckets/BucketPage.d.ts +2 -0
- package/dist/components/buckets/BucketPage.js +47 -0
- package/dist/components/buckets/BucketPolicyButton.d.ts +7 -0
- package/dist/components/buckets/BucketPolicyButton.js +18 -0
- package/dist/components/buckets/BucketPolicyPage.d.ts +1 -0
- package/dist/components/buckets/BucketPolicyPage.js +205 -0
- package/dist/components/buckets/DeleteBucketButton.d.ts +8 -0
- package/dist/components/buckets/DeleteBucketButton.js +78 -0
- package/dist/components/index.d.ts +12 -0
- package/dist/components/index.js +13 -0
- package/dist/components/layouts/BrowserPageLayout.d.ts +9 -0
- package/dist/components/layouts/BrowserPageLayout.js +46 -0
- package/dist/components/objects/CreateFolderButton.d.ts +29 -0
- package/dist/components/objects/CreateFolderButton.js +118 -0
- package/dist/components/objects/DeleteObjectButton.d.ts +8 -0
- package/dist/components/objects/DeleteObjectButton.js +191 -0
- package/dist/components/objects/ObjectDetails/ObjectMetadata.d.ts +2 -0
- package/dist/components/objects/ObjectDetails/ObjectMetadata.js +323 -0
- package/dist/components/objects/ObjectDetails/ObjectSummary.d.ts +3 -0
- package/dist/components/objects/ObjectDetails/ObjectSummary.js +193 -0
- package/dist/components/objects/ObjectDetails/ObjectTags.d.ts +3 -0
- package/dist/components/objects/ObjectDetails/ObjectTags.js +300 -0
- package/dist/components/objects/ObjectDetails/index.d.ts +9 -0
- package/dist/components/objects/ObjectDetails/index.js +49 -0
- package/dist/components/objects/ObjectList.d.ts +40 -0
- package/dist/components/objects/ObjectList.js +407 -0
- package/dist/components/objects/ObjectPage.d.ts +1 -0
- package/dist/components/objects/ObjectPage.js +43 -0
- package/dist/components/objects/UploadButton.d.ts +34 -0
- package/dist/components/objects/UploadButton.js +229 -0
- package/dist/components/providers/DataBrowserProvider.d.ts +20 -0
- package/dist/components/providers/DataBrowserProvider.js +42 -0
- package/dist/components/search/MetadataSearch.d.ts +5 -0
- package/dist/components/search/MetadataSearch.js +162 -0
- package/dist/components/search/SearchHints.d.ts +8 -0
- package/dist/components/search/SearchHints.js +21 -0
- package/dist/components/ui/DeleteObjectModalContent.d.ts +5 -0
- package/dist/components/ui/DeleteObjectModalContent.js +71 -0
- package/dist/components/ui/Search.elements.d.ts +17 -0
- package/dist/components/ui/Search.elements.js +59 -0
- package/dist/components/ui/Table.elements.d.ts +36 -0
- package/dist/components/ui/Table.elements.js +87 -0
- package/dist/config/factory.d.ts +52 -0
- package/dist/config/factory.js +70 -0
- package/dist/config/types.d.ts +46 -0
- package/dist/config/types.js +0 -0
- package/dist/hooks/__tests__/useIsBucketEmpty.test.d.ts +1 -0
- package/dist/hooks/__tests__/useIsBucketEmpty.test.js +122 -0
- package/dist/hooks/bucketConfiguration.d.ts +147 -0
- package/dist/hooks/bucketConfiguration.js +59 -0
- package/dist/hooks/bucketOperations.d.ts +36 -0
- package/dist/hooks/bucketOperations.js +12 -0
- package/dist/hooks/factories/__tests__/useCreateS3FunctionMutationHook.test.d.ts +1 -0
- package/dist/hooks/factories/__tests__/useCreateS3FunctionMutationHook.test.js +276 -0
- package/dist/hooks/factories/__tests__/useCreateS3InfiniteQueryHook.test.d.ts +1 -0
- package/dist/hooks/factories/__tests__/useCreateS3InfiniteQueryHook.test.js +259 -0
- package/dist/hooks/factories/__tests__/useCreateS3LoginHook.test.d.ts +1 -0
- package/dist/hooks/factories/__tests__/useCreateS3LoginHook.test.js +166 -0
- package/dist/hooks/factories/__tests__/useCreateS3MutationHook.test.d.ts +1 -0
- package/dist/hooks/factories/__tests__/useCreateS3MutationHook.test.js +200 -0
- package/dist/hooks/factories/__tests__/useCreateS3QueryHook.test.d.ts +1 -0
- package/dist/hooks/factories/__tests__/useCreateS3QueryHook.test.js +136 -0
- package/dist/hooks/factories/index.d.ts +18 -0
- package/dist/hooks/factories/index.js +5 -0
- package/dist/hooks/factories/useCreateS3InfiniteQueryHook.d.ts +13 -0
- package/dist/hooks/factories/useCreateS3InfiniteQueryHook.js +76 -0
- package/dist/hooks/factories/useCreateS3LoginHook.d.ts +8 -0
- package/dist/hooks/factories/useCreateS3LoginHook.js +22 -0
- package/dist/hooks/factories/useCreateS3MutationHook.d.ts +5 -0
- package/dist/hooks/factories/useCreateS3MutationHook.js +50 -0
- package/dist/hooks/factories/useCreateS3QueryHook.d.ts +3 -0
- package/dist/hooks/factories/useCreateS3QueryHook.js +30 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.js +8 -0
- package/dist/hooks/loginOperations.d.ts +21 -0
- package/dist/hooks/loginOperations.js +9 -0
- package/dist/hooks/objectOperations.d.ts +190 -0
- package/dist/hooks/objectOperations.js +66 -0
- package/dist/hooks/presignedOperations.d.ts +73 -0
- package/dist/hooks/presignedOperations.js +72 -0
- package/dist/hooks/useIsBucketEmpty.d.ts +7 -0
- package/dist/hooks/useIsBucketEmpty.js +36 -0
- package/dist/hooks/useLoginMutation.d.ts +21 -0
- package/dist/hooks/useLoginMutation.js +9 -0
- package/dist/hooks/useS3Client.d.ts +1 -0
- package/dist/hooks/useS3Client.js +13 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/schemas/bucketPolicySchema.json +321 -0
- package/dist/test/msw/handlers/deleteBucket.d.ts +1 -0
- package/dist/test/msw/handlers/deleteBucket.js +14 -0
- package/dist/test/msw/handlers/getBucketAcl.d.ts +1 -0
- package/dist/test/msw/handlers/getBucketAcl.js +96 -0
- package/dist/test/msw/handlers/getBucketLocation.d.ts +1 -0
- package/dist/test/msw/handlers/getBucketLocation.js +23 -0
- package/dist/test/msw/handlers/getBucketPolicy.d.ts +11 -0
- package/dist/test/msw/handlers/getBucketPolicy.js +72 -0
- package/dist/test/msw/handlers/headObject.d.ts +1 -0
- package/dist/test/msw/handlers/headObject.js +17 -0
- package/dist/test/msw/handlers/listBuckets.d.ts +1 -0
- package/dist/test/msw/handlers/listBuckets.js +24 -0
- package/dist/test/msw/handlers/listObjectVersions.d.ts +1 -0
- package/dist/test/msw/handlers/listObjectVersions.js +83 -0
- package/dist/test/msw/handlers/listObjects.d.ts +1 -0
- package/dist/test/msw/handlers/listObjects.js +66 -0
- package/dist/test/msw/handlers/objectLegalHold.d.ts +1 -0
- package/dist/test/msw/handlers/objectLegalHold.js +24 -0
- package/dist/test/msw/handlers/objectRetention.d.ts +1 -0
- package/dist/test/msw/handlers/objectRetention.js +27 -0
- package/dist/test/msw/handlers/putBucketAcl.d.ts +1 -0
- package/dist/test/msw/handlers/putBucketAcl.js +18 -0
- package/dist/test/msw/handlers/putObject.d.ts +1 -0
- package/dist/test/msw/handlers/putObject.js +16 -0
- package/dist/test/msw/handlers.d.ts +4 -0
- package/dist/test/msw/handlers.js +109 -0
- package/dist/test/msw/index.d.ts +2 -0
- package/dist/test/msw/index.js +3 -0
- package/dist/test/msw/server.d.ts +4 -0
- package/dist/test/msw/server.js +20 -0
- package/dist/test/msw/utils.d.ts +2 -0
- package/dist/test/msw/utils.js +13 -0
- package/dist/test/setup.d.ts +1 -0
- package/dist/test/setup.js +82 -0
- package/dist/test/testUtils.d.ts +82 -0
- package/dist/test/testUtils.js +236 -0
- package/dist/test/utils/errorHandling.test.d.ts +1 -0
- package/dist/test/utils/errorHandling.test.js +385 -0
- package/dist/types/index.d.ts +48 -0
- package/dist/types/index.js +0 -0
- package/dist/utils/deletion/index.d.ts +2 -0
- package/dist/utils/deletion/index.js +2 -0
- package/dist/utils/deletion/messages.d.ts +5 -0
- package/dist/utils/deletion/messages.js +29 -0
- package/dist/utils/deletion/types.d.ts +11 -0
- package/dist/utils/deletion/types.js +0 -0
- package/dist/utils/errorHandling.d.ts +54 -0
- package/dist/utils/errorHandling.js +79 -0
- package/dist/utils/hooks.d.ts +2 -0
- package/dist/utils/hooks.js +26 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/proxyMiddleware.d.ts +18 -0
- package/dist/utils/proxyMiddleware.js +56 -0
- package/dist/utils/s3Client.d.ts +5 -0
- package/dist/utils/s3Client.js +35 -0
- package/dist/utils/useFeatures.d.ts +1 -0
- package/dist/utils/useFeatures.js +7 -0
- package/package.json +79 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import { useGetBucketAcl, useGetBucketCors, useGetBucketLocation, useGetBucketObjectLockConfiguration, useGetBucketPolicy, useGetBucketVersioning } from "../../hooks/index.js";
|
|
4
|
+
import { createTestWrapper } from "../../test/testUtils.js";
|
|
5
|
+
import { BucketOverview } from "../buckets/BucketOverview.js";
|
|
6
|
+
jest.mock("../../hooks");
|
|
7
|
+
const mockUseGetBucketVersioning = jest.mocked(useGetBucketVersioning);
|
|
8
|
+
const mockUseGetBucketAcl = jest.mocked(useGetBucketAcl);
|
|
9
|
+
const mockUseGetBucketLocation = jest.mocked(useGetBucketLocation);
|
|
10
|
+
const mockUseGetBucketCors = jest.mocked(useGetBucketCors);
|
|
11
|
+
const mockUseGetBucketObjectLockConfiguration = jest.mocked(useGetBucketObjectLockConfiguration);
|
|
12
|
+
const mockUseGetBucketPolicy = jest.mocked(useGetBucketPolicy);
|
|
13
|
+
const renderBucketOverview = (props = {})=>{
|
|
14
|
+
const Wrapper = createTestWrapper();
|
|
15
|
+
return render(/*#__PURE__*/ jsx(Wrapper, {
|
|
16
|
+
children: /*#__PURE__*/ jsx(BucketOverview, {
|
|
17
|
+
bucketName: "test-bucket",
|
|
18
|
+
...props
|
|
19
|
+
})
|
|
20
|
+
}));
|
|
21
|
+
};
|
|
22
|
+
const mockHookDefaults = ()=>{
|
|
23
|
+
mockUseGetBucketVersioning.mockReturnValue({
|
|
24
|
+
data: {
|
|
25
|
+
Status: "Enabled"
|
|
26
|
+
},
|
|
27
|
+
status: "success"
|
|
28
|
+
});
|
|
29
|
+
mockUseGetBucketAcl.mockReturnValue({
|
|
30
|
+
data: {
|
|
31
|
+
Owner: {
|
|
32
|
+
DisplayName: "test-owner"
|
|
33
|
+
},
|
|
34
|
+
Grants: [
|
|
35
|
+
{
|
|
36
|
+
Grantee: {
|
|
37
|
+
DisplayName: "test-grantee"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
status: "success"
|
|
43
|
+
});
|
|
44
|
+
mockUseGetBucketLocation.mockReturnValue({
|
|
45
|
+
data: {
|
|
46
|
+
LocationConstraint: "us-west-2"
|
|
47
|
+
},
|
|
48
|
+
status: "success"
|
|
49
|
+
});
|
|
50
|
+
mockUseGetBucketCors.mockReturnValue({
|
|
51
|
+
data: {
|
|
52
|
+
CORSRules: []
|
|
53
|
+
},
|
|
54
|
+
status: "success"
|
|
55
|
+
});
|
|
56
|
+
mockUseGetBucketObjectLockConfiguration.mockReturnValue({
|
|
57
|
+
data: {
|
|
58
|
+
ObjectLockConfiguration: {
|
|
59
|
+
ObjectLockEnabled: "Disabled"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
status: "success"
|
|
63
|
+
});
|
|
64
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
65
|
+
data: void 0,
|
|
66
|
+
error: null,
|
|
67
|
+
status: "success"
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
describe("BucketOverview", ()=>{
|
|
71
|
+
beforeEach(()=>{
|
|
72
|
+
jest.clearAllMocks();
|
|
73
|
+
mockHookDefaults();
|
|
74
|
+
});
|
|
75
|
+
it("renders bucket overview with all sections", ()=>{
|
|
76
|
+
renderBucketOverview();
|
|
77
|
+
expect(screen.getByText("General")).toBeInTheDocument();
|
|
78
|
+
expect(screen.getByText("Data protection")).toBeInTheDocument();
|
|
79
|
+
expect(screen.getByText("Permissions")).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
it("displays bucket name correctly", ()=>{
|
|
82
|
+
renderBucketOverview({
|
|
83
|
+
bucketName: "my-test-bucket"
|
|
84
|
+
});
|
|
85
|
+
expect(screen.getByText("my-test-bucket")).toBeInTheDocument();
|
|
86
|
+
});
|
|
87
|
+
it("shows versioning status when enabled", ()=>{
|
|
88
|
+
mockUseGetBucketVersioning.mockReturnValue({
|
|
89
|
+
data: {
|
|
90
|
+
Status: "Enabled"
|
|
91
|
+
},
|
|
92
|
+
status: "success"
|
|
93
|
+
});
|
|
94
|
+
renderBucketOverview();
|
|
95
|
+
expect(screen.getByText("Active")).toBeInTheDocument();
|
|
96
|
+
});
|
|
97
|
+
it("shows versioning status when disabled", ()=>{
|
|
98
|
+
mockUseGetBucketVersioning.mockReturnValue({
|
|
99
|
+
data: {
|
|
100
|
+
Status: "Suspended"
|
|
101
|
+
},
|
|
102
|
+
status: "success"
|
|
103
|
+
});
|
|
104
|
+
renderBucketOverview();
|
|
105
|
+
expect(screen.getByText("Inactive")).toBeInTheDocument();
|
|
106
|
+
});
|
|
107
|
+
it("displays bucket location", ()=>{
|
|
108
|
+
mockUseGetBucketLocation.mockReturnValue({
|
|
109
|
+
data: {
|
|
110
|
+
LocationConstraint: "eu-west-1"
|
|
111
|
+
},
|
|
112
|
+
status: "success"
|
|
113
|
+
});
|
|
114
|
+
renderBucketOverview();
|
|
115
|
+
expect(screen.getByText("eu-west-1")).toBeInTheDocument();
|
|
116
|
+
});
|
|
117
|
+
it("shows default location when LocationConstraint is null", ()=>{
|
|
118
|
+
mockUseGetBucketLocation.mockReturnValue({
|
|
119
|
+
data: {
|
|
120
|
+
LocationConstraint: null
|
|
121
|
+
},
|
|
122
|
+
status: "success"
|
|
123
|
+
});
|
|
124
|
+
renderBucketOverview();
|
|
125
|
+
expect(screen.getByText("us-east-1")).toBeInTheDocument();
|
|
126
|
+
});
|
|
127
|
+
it("shows object-lock enabled status", ()=>{
|
|
128
|
+
mockUseGetBucketObjectLockConfiguration.mockReturnValue({
|
|
129
|
+
data: {
|
|
130
|
+
ObjectLockConfiguration: {
|
|
131
|
+
ObjectLockEnabled: "Enabled"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
status: "success"
|
|
135
|
+
});
|
|
136
|
+
renderBucketOverview();
|
|
137
|
+
expect(screen.getByText("Enabled")).toBeInTheDocument();
|
|
138
|
+
});
|
|
139
|
+
it("shows object-lock disabled status", ()=>{
|
|
140
|
+
mockUseGetBucketObjectLockConfiguration.mockReturnValue({
|
|
141
|
+
data: {
|
|
142
|
+
ObjectLockConfiguration: {
|
|
143
|
+
ObjectLockEnabled: "Disabled"
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
status: "success"
|
|
147
|
+
});
|
|
148
|
+
renderBucketOverview();
|
|
149
|
+
expect(screen.getByText("Disabled")).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
it("displays bucket owner from ACL", ()=>{
|
|
152
|
+
mockUseGetBucketAcl.mockReturnValue({
|
|
153
|
+
data: {
|
|
154
|
+
Owner: {
|
|
155
|
+
DisplayName: "bucket-owner"
|
|
156
|
+
},
|
|
157
|
+
Grants: []
|
|
158
|
+
},
|
|
159
|
+
status: "success"
|
|
160
|
+
});
|
|
161
|
+
renderBucketOverview();
|
|
162
|
+
expect(screen.getByText("bucket-owner")).toBeInTheDocument();
|
|
163
|
+
});
|
|
164
|
+
it("shows ACL grantees count", ()=>{
|
|
165
|
+
mockUseGetBucketAcl.mockReturnValue({
|
|
166
|
+
data: {
|
|
167
|
+
Owner: {
|
|
168
|
+
DisplayName: "owner"
|
|
169
|
+
},
|
|
170
|
+
Grants: [
|
|
171
|
+
{
|
|
172
|
+
Grantee: {
|
|
173
|
+
DisplayName: "user1"
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
Grantee: {
|
|
178
|
+
DisplayName: "user2"
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
Grantee: {
|
|
183
|
+
DisplayName: "user3"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
status: "success"
|
|
189
|
+
});
|
|
190
|
+
renderBucketOverview();
|
|
191
|
+
expect(screen.getByText("3 Grantees")).toBeInTheDocument();
|
|
192
|
+
});
|
|
193
|
+
it("shows single grantee without plural", ()=>{
|
|
194
|
+
mockUseGetBucketAcl.mockReturnValue({
|
|
195
|
+
data: {
|
|
196
|
+
Owner: {
|
|
197
|
+
DisplayName: "owner"
|
|
198
|
+
},
|
|
199
|
+
Grants: [
|
|
200
|
+
{
|
|
201
|
+
Grantee: {
|
|
202
|
+
DisplayName: "user1"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
},
|
|
207
|
+
status: "success"
|
|
208
|
+
});
|
|
209
|
+
renderBucketOverview();
|
|
210
|
+
expect(screen.getByText("1 Grantee")).toBeInTheDocument();
|
|
211
|
+
});
|
|
212
|
+
it("shows CORS enabled when rules exist", ()=>{
|
|
213
|
+
mockUseGetBucketCors.mockReturnValue({
|
|
214
|
+
data: {
|
|
215
|
+
CORSRules: [
|
|
216
|
+
{
|
|
217
|
+
AllowedMethods: [
|
|
218
|
+
"GET"
|
|
219
|
+
],
|
|
220
|
+
AllowedOrigins: [
|
|
221
|
+
"*"
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
},
|
|
226
|
+
status: "success"
|
|
227
|
+
});
|
|
228
|
+
renderBucketOverview();
|
|
229
|
+
expect(screen.getByText("Yes")).toBeInTheDocument();
|
|
230
|
+
});
|
|
231
|
+
it("shows CORS disabled when no rules exist", ()=>{
|
|
232
|
+
mockUseGetBucketCors.mockReturnValue({
|
|
233
|
+
data: {
|
|
234
|
+
CORSRules: []
|
|
235
|
+
},
|
|
236
|
+
status: "success"
|
|
237
|
+
});
|
|
238
|
+
renderBucketOverview();
|
|
239
|
+
const corsValues = screen.getAllByText("No");
|
|
240
|
+
expect(corsValues.length).toBeGreaterThan(0);
|
|
241
|
+
});
|
|
242
|
+
it("detects public bucket from ACL grants", ()=>{
|
|
243
|
+
mockUseGetBucketAcl.mockReturnValue({
|
|
244
|
+
data: {
|
|
245
|
+
Owner: {
|
|
246
|
+
DisplayName: "owner"
|
|
247
|
+
},
|
|
248
|
+
Grants: [
|
|
249
|
+
{
|
|
250
|
+
Grantee: {
|
|
251
|
+
URI: "http://acs.amazonaws.com/groups/global/AllUsers"
|
|
252
|
+
},
|
|
253
|
+
Permission: "READ"
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
},
|
|
257
|
+
status: "success"
|
|
258
|
+
});
|
|
259
|
+
renderBucketOverview();
|
|
260
|
+
expect(screen.getByText("Yes")).toBeInTheDocument();
|
|
261
|
+
});
|
|
262
|
+
it("shows non-public bucket when no public grants", ()=>{
|
|
263
|
+
mockUseGetBucketAcl.mockReturnValue({
|
|
264
|
+
data: {
|
|
265
|
+
Owner: {
|
|
266
|
+
DisplayName: "owner"
|
|
267
|
+
},
|
|
268
|
+
Grants: [
|
|
269
|
+
{
|
|
270
|
+
Grantee: {
|
|
271
|
+
DisplayName: "private-user"
|
|
272
|
+
},
|
|
273
|
+
Permission: "READ"
|
|
274
|
+
}
|
|
275
|
+
]
|
|
276
|
+
},
|
|
277
|
+
status: "success"
|
|
278
|
+
});
|
|
279
|
+
renderBucketOverview();
|
|
280
|
+
const publicValues = screen.getAllByText("No");
|
|
281
|
+
expect(publicValues.length).toBeGreaterThan(0);
|
|
282
|
+
});
|
|
283
|
+
it("shows loading states while data is being fetched", ()=>{
|
|
284
|
+
mockUseGetBucketVersioning.mockReturnValue({
|
|
285
|
+
data: void 0,
|
|
286
|
+
status: "pending"
|
|
287
|
+
});
|
|
288
|
+
mockUseGetBucketAcl.mockReturnValue({
|
|
289
|
+
data: void 0,
|
|
290
|
+
status: "pending"
|
|
291
|
+
});
|
|
292
|
+
renderBucketOverview();
|
|
293
|
+
const loaders = document.querySelectorAll('[data-testid="loader"], .loader, [class*="loader"]');
|
|
294
|
+
expect(loaders.length).toBeGreaterThan(0);
|
|
295
|
+
});
|
|
296
|
+
it("shows error states when API calls fail", ()=>{
|
|
297
|
+
mockUseGetBucketVersioning.mockReturnValue({
|
|
298
|
+
data: void 0,
|
|
299
|
+
status: "error"
|
|
300
|
+
});
|
|
301
|
+
mockUseGetBucketLocation.mockReturnValue({
|
|
302
|
+
data: void 0,
|
|
303
|
+
status: "error"
|
|
304
|
+
});
|
|
305
|
+
renderBucketOverview();
|
|
306
|
+
const errorElements = screen.getAllByText("Error");
|
|
307
|
+
expect(errorElements.length).toBeGreaterThan(0);
|
|
308
|
+
});
|
|
309
|
+
it("handles empty bucket button click", ()=>{
|
|
310
|
+
const onEmptyBucket = jest.fn();
|
|
311
|
+
renderBucketOverview({
|
|
312
|
+
onEmptyBucket,
|
|
313
|
+
isEmptyBucketDisabled: false
|
|
314
|
+
});
|
|
315
|
+
const emptyButton = screen.getByRole("button", {
|
|
316
|
+
name: /empty bucket/i
|
|
317
|
+
});
|
|
318
|
+
fireEvent.click(emptyButton);
|
|
319
|
+
expect(onEmptyBucket).toHaveBeenCalled();
|
|
320
|
+
});
|
|
321
|
+
it("handles delete bucket button click", ()=>{
|
|
322
|
+
const onDeleteBucket = jest.fn();
|
|
323
|
+
renderBucketOverview({
|
|
324
|
+
onDeleteBucket
|
|
325
|
+
});
|
|
326
|
+
const deleteButton = screen.getByRole("button", {
|
|
327
|
+
name: /delete bucket/i
|
|
328
|
+
});
|
|
329
|
+
fireEvent.click(deleteButton);
|
|
330
|
+
expect(onDeleteBucket).toHaveBeenCalled();
|
|
331
|
+
});
|
|
332
|
+
it("disables empty button when specified", ()=>{
|
|
333
|
+
renderBucketOverview({
|
|
334
|
+
isEmptyBucketDisabled: true
|
|
335
|
+
});
|
|
336
|
+
const emptyButton = screen.getByRole("button", {
|
|
337
|
+
name: /empty bucket/i
|
|
338
|
+
});
|
|
339
|
+
expect(emptyButton).toBeDisabled();
|
|
340
|
+
});
|
|
341
|
+
it("disables delete button when specified", ()=>{
|
|
342
|
+
renderBucketOverview({
|
|
343
|
+
isDeleteBucketDisabled: true
|
|
344
|
+
});
|
|
345
|
+
const deleteButton = screen.getByRole("button", {
|
|
346
|
+
name: /delete bucket/i
|
|
347
|
+
});
|
|
348
|
+
expect(deleteButton).toBeDisabled();
|
|
349
|
+
});
|
|
350
|
+
it("renders custom empty button when provided", ()=>{
|
|
351
|
+
const renderEmptyButton = jest.fn(()=>/*#__PURE__*/ jsx("button", {
|
|
352
|
+
children: "Custom Empty Button"
|
|
353
|
+
}));
|
|
354
|
+
renderBucketOverview({
|
|
355
|
+
renderEmptyButton
|
|
356
|
+
});
|
|
357
|
+
expect(screen.getByText("Custom Empty Button")).toBeInTheDocument();
|
|
358
|
+
expect(renderEmptyButton).toHaveBeenCalledWith("test-bucket");
|
|
359
|
+
});
|
|
360
|
+
it("renders custom delete button when provided", ()=>{
|
|
361
|
+
const renderDeleteButton = jest.fn(()=>/*#__PURE__*/ jsx("button", {
|
|
362
|
+
children: "Custom Delete Button"
|
|
363
|
+
}));
|
|
364
|
+
renderBucketOverview({
|
|
365
|
+
renderDeleteButton
|
|
366
|
+
});
|
|
367
|
+
expect(screen.getByText("Custom Delete Button")).toBeInTheDocument();
|
|
368
|
+
expect(renderDeleteButton).toHaveBeenCalledWith("test-bucket");
|
|
369
|
+
});
|
|
370
|
+
it("works when no callbacks are provided", ()=>{
|
|
371
|
+
expect(()=>{
|
|
372
|
+
renderBucketOverview();
|
|
373
|
+
const emptyButton = screen.getByRole("button", {
|
|
374
|
+
name: /empty bucket/i
|
|
375
|
+
});
|
|
376
|
+
const deleteButton = screen.getByRole("button", {
|
|
377
|
+
name: /delete bucket/i
|
|
378
|
+
});
|
|
379
|
+
fireEvent.click(emptyButton);
|
|
380
|
+
fireEvent.click(deleteButton);
|
|
381
|
+
}).not.toThrow();
|
|
382
|
+
});
|
|
383
|
+
it("handles missing ACL data gracefully", ()=>{
|
|
384
|
+
mockUseGetBucketAcl.mockReturnValue({
|
|
385
|
+
data: void 0,
|
|
386
|
+
status: "success"
|
|
387
|
+
});
|
|
388
|
+
renderBucketOverview();
|
|
389
|
+
const naElements = screen.getAllByText("N/A");
|
|
390
|
+
expect(naElements.length).toBeGreaterThan(0);
|
|
391
|
+
});
|
|
392
|
+
it("handles missing versioning data gracefully", ()=>{
|
|
393
|
+
mockUseGetBucketVersioning.mockReturnValue({
|
|
394
|
+
data: void 0,
|
|
395
|
+
status: "success"
|
|
396
|
+
});
|
|
397
|
+
renderBucketOverview();
|
|
398
|
+
expect(screen.getByText("Inactive")).toBeInTheDocument();
|
|
399
|
+
});
|
|
400
|
+
it("handles CORS error state", ()=>{
|
|
401
|
+
mockUseGetBucketCors.mockReturnValue({
|
|
402
|
+
data: void 0,
|
|
403
|
+
status: "error"
|
|
404
|
+
});
|
|
405
|
+
renderBucketOverview();
|
|
406
|
+
const corsValues = screen.getAllByText("No");
|
|
407
|
+
expect(corsValues.length).toBeGreaterThan(0);
|
|
408
|
+
});
|
|
409
|
+
it("handles object lock error state", ()=>{
|
|
410
|
+
mockUseGetBucketObjectLockConfiguration.mockReturnValue({
|
|
411
|
+
data: void 0,
|
|
412
|
+
status: "error"
|
|
413
|
+
});
|
|
414
|
+
renderBucketOverview();
|
|
415
|
+
expect(screen.getByText("Disabled")).toBeInTheDocument();
|
|
416
|
+
});
|
|
417
|
+
describe("Bucket Policy", ()=>{
|
|
418
|
+
it("shows 'Configured' when policy exists", ()=>{
|
|
419
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
420
|
+
data: {
|
|
421
|
+
Policy: '{"Version":"2012-10-17","Statement":[]}'
|
|
422
|
+
},
|
|
423
|
+
error: null,
|
|
424
|
+
status: "success"
|
|
425
|
+
});
|
|
426
|
+
renderBucketOverview();
|
|
427
|
+
expect(screen.getByText("Configured")).toBeInTheDocument();
|
|
428
|
+
});
|
|
429
|
+
it("shows 'Not configured' when no policy exists", ()=>{
|
|
430
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
431
|
+
data: void 0,
|
|
432
|
+
error: null,
|
|
433
|
+
status: "success"
|
|
434
|
+
});
|
|
435
|
+
renderBucketOverview();
|
|
436
|
+
expect(screen.getByText("Not configured")).toBeInTheDocument();
|
|
437
|
+
});
|
|
438
|
+
it("shows 'Error' when policy fetch fails with non-NoSuchBucketPolicy error", ()=>{
|
|
439
|
+
const genericError = new Error("Network error");
|
|
440
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
441
|
+
data: void 0,
|
|
442
|
+
error: genericError,
|
|
443
|
+
status: "error"
|
|
444
|
+
});
|
|
445
|
+
renderBucketOverview();
|
|
446
|
+
const errorElements = screen.getAllByText("Error");
|
|
447
|
+
expect(errorElements.length).toBeGreaterThan(0);
|
|
448
|
+
});
|
|
449
|
+
it("shows 'Not configured' when policy does not exist (NoSuchBucketPolicy)", ()=>{
|
|
450
|
+
const noSuchPolicyError = new Error("Policy does not exist");
|
|
451
|
+
noSuchPolicyError.name = "NoSuchBucketPolicy";
|
|
452
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
453
|
+
data: void 0,
|
|
454
|
+
error: noSuchPolicyError,
|
|
455
|
+
status: "error"
|
|
456
|
+
});
|
|
457
|
+
renderBucketOverview();
|
|
458
|
+
expect(screen.getByText("Not configured")).toBeInTheDocument();
|
|
459
|
+
});
|
|
460
|
+
it("calls onEditPolicy when edit button is clicked", ()=>{
|
|
461
|
+
const onEditPolicy = jest.fn();
|
|
462
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
463
|
+
data: {
|
|
464
|
+
Policy: '{"Version":"2012-10-17","Statement":[]}'
|
|
465
|
+
},
|
|
466
|
+
error: null,
|
|
467
|
+
status: "success"
|
|
468
|
+
});
|
|
469
|
+
renderBucketOverview({
|
|
470
|
+
onEditPolicy
|
|
471
|
+
});
|
|
472
|
+
const editButton = screen.getByRole("button", {
|
|
473
|
+
name: /edit/i
|
|
474
|
+
});
|
|
475
|
+
fireEvent.click(editButton);
|
|
476
|
+
expect(onEditPolicy).toHaveBeenCalledWith("test-bucket");
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
|
4
|
+
import { createTestWrapper } from "../../test/testUtils.js";
|
|
5
|
+
import { BucketPolicyPage } from "../buckets/BucketPolicyPage.js";
|
|
6
|
+
import { useGetBucketPolicy, useSetBucketPolicy } from "../../hooks/index.js";
|
|
7
|
+
jest.mock("../../hooks", ()=>({
|
|
8
|
+
useGetBucketPolicy: jest.fn(),
|
|
9
|
+
useSetBucketPolicy: jest.fn()
|
|
10
|
+
}));
|
|
11
|
+
jest.mock("../Editor", ()=>({
|
|
12
|
+
Editor: ({ value, onChange })=>/*#__PURE__*/ jsx("textarea", {
|
|
13
|
+
"data-testid": "policy-editor",
|
|
14
|
+
value: value,
|
|
15
|
+
onChange: (e)=>onChange(e.target.value)
|
|
16
|
+
})
|
|
17
|
+
}));
|
|
18
|
+
const mockUseGetBucketPolicy = jest.mocked(useGetBucketPolicy);
|
|
19
|
+
const mockUseSetBucketPolicy = jest.mocked(useSetBucketPolicy);
|
|
20
|
+
const mockNavigate = jest.fn();
|
|
21
|
+
jest.mock("react-router-dom", ()=>({
|
|
22
|
+
...jest.requireActual("react-router-dom"),
|
|
23
|
+
useNavigate: ()=>mockNavigate
|
|
24
|
+
}));
|
|
25
|
+
const existingPolicy = {
|
|
26
|
+
Version: "2012-10-17",
|
|
27
|
+
Statement: [
|
|
28
|
+
{
|
|
29
|
+
Sid: "ExistingStatement",
|
|
30
|
+
Effect: "Allow",
|
|
31
|
+
Principal: "*",
|
|
32
|
+
Action: "s3:GetObject",
|
|
33
|
+
Resource: "arn:aws:s3:::test-bucket/*"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
const renderBucketPolicyPage = (bucketName = "test-bucket")=>{
|
|
38
|
+
const Wrapper = createTestWrapper();
|
|
39
|
+
return render(/*#__PURE__*/ jsx(MemoryRouter, {
|
|
40
|
+
initialEntries: [
|
|
41
|
+
`/buckets/${bucketName}/policy`
|
|
42
|
+
],
|
|
43
|
+
children: /*#__PURE__*/ jsx(Wrapper, {
|
|
44
|
+
children: /*#__PURE__*/ jsx(Routes, {
|
|
45
|
+
children: /*#__PURE__*/ jsx(Route, {
|
|
46
|
+
path: "/buckets/:bucketName/policy",
|
|
47
|
+
element: /*#__PURE__*/ jsx(BucketPolicyPage, {})
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
}));
|
|
52
|
+
};
|
|
53
|
+
describe("BucketPolicyPage", ()=>{
|
|
54
|
+
const mockMutate = jest.fn();
|
|
55
|
+
beforeEach(()=>{
|
|
56
|
+
jest.clearAllMocks();
|
|
57
|
+
mockNavigate.mockClear();
|
|
58
|
+
mockUseSetBucketPolicy.mockReturnValue({
|
|
59
|
+
mutate: mockMutate,
|
|
60
|
+
isPending: false
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
it("shows loading state while fetching policy", ()=>{
|
|
64
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
65
|
+
data: void 0,
|
|
66
|
+
status: "pending",
|
|
67
|
+
error: null
|
|
68
|
+
});
|
|
69
|
+
renderBucketPolicyPage();
|
|
70
|
+
expect(screen.getByText("Loading policy...")).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
it("renders create mode with default policy template when no policy exists", async ()=>{
|
|
73
|
+
const noSuchPolicyError = new Error("Policy does not exist");
|
|
74
|
+
noSuchPolicyError.name = "NoSuchBucketPolicy";
|
|
75
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
76
|
+
data: void 0,
|
|
77
|
+
status: "error",
|
|
78
|
+
error: noSuchPolicyError
|
|
79
|
+
});
|
|
80
|
+
renderBucketPolicyPage();
|
|
81
|
+
await waitFor(()=>{
|
|
82
|
+
expect(screen.getByText("Bucket Policy Creation")).toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
const editor = screen.getByTestId("policy-editor");
|
|
85
|
+
expect(editor.value).toContain("ExampleStatement");
|
|
86
|
+
});
|
|
87
|
+
it("renders edit mode with existing policy", async ()=>{
|
|
88
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
89
|
+
data: {
|
|
90
|
+
Policy: JSON.stringify(existingPolicy)
|
|
91
|
+
},
|
|
92
|
+
status: "success",
|
|
93
|
+
error: null
|
|
94
|
+
});
|
|
95
|
+
renderBucketPolicyPage();
|
|
96
|
+
await waitFor(()=>{
|
|
97
|
+
expect(screen.getByText("Bucket Policy Edition")).toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
const editor = screen.getByTestId("policy-editor");
|
|
100
|
+
expect(JSON.parse(editor.value)).toEqual(existingPolicy);
|
|
101
|
+
});
|
|
102
|
+
it("validates JSON format and shows error for invalid input", async ()=>{
|
|
103
|
+
const noSuchPolicyError = new Error("Policy does not exist");
|
|
104
|
+
noSuchPolicyError.name = "NoSuchBucketPolicy";
|
|
105
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
106
|
+
data: void 0,
|
|
107
|
+
status: "error",
|
|
108
|
+
error: noSuchPolicyError
|
|
109
|
+
});
|
|
110
|
+
renderBucketPolicyPage();
|
|
111
|
+
await waitFor(()=>{
|
|
112
|
+
expect(screen.getByTestId("policy-editor")).toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
const editor = screen.getByTestId("policy-editor");
|
|
115
|
+
fireEvent.change(editor, {
|
|
116
|
+
target: {
|
|
117
|
+
value: "{ invalid json"
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
await waitFor(()=>{
|
|
121
|
+
expect(screen.getByText(/Invalid JSON format/i)).toBeInTheDocument();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
it("saves valid policy and navigates back on success", async ()=>{
|
|
125
|
+
const noSuchPolicyError = new Error("Policy does not exist");
|
|
126
|
+
noSuchPolicyError.name = "NoSuchBucketPolicy";
|
|
127
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
128
|
+
data: void 0,
|
|
129
|
+
status: "error",
|
|
130
|
+
error: noSuchPolicyError
|
|
131
|
+
});
|
|
132
|
+
renderBucketPolicyPage();
|
|
133
|
+
await waitFor(()=>{
|
|
134
|
+
expect(screen.getByTestId("policy-editor")).toBeInTheDocument();
|
|
135
|
+
});
|
|
136
|
+
const editor = screen.getByTestId("policy-editor");
|
|
137
|
+
const validPolicy = JSON.stringify(existingPolicy, null, 2);
|
|
138
|
+
fireEvent.change(editor, {
|
|
139
|
+
target: {
|
|
140
|
+
value: validPolicy
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
mockMutate.mockImplementation((_, options)=>{
|
|
144
|
+
options?.onSuccess?.();
|
|
145
|
+
});
|
|
146
|
+
const saveButton = screen.getByRole("button", {
|
|
147
|
+
name: /save/i
|
|
148
|
+
});
|
|
149
|
+
await waitFor(()=>{
|
|
150
|
+
expect(saveButton).not.toBeDisabled();
|
|
151
|
+
});
|
|
152
|
+
fireEvent.click(saveButton);
|
|
153
|
+
await waitFor(()=>{
|
|
154
|
+
expect(mockMutate).toHaveBeenCalledWith({
|
|
155
|
+
Bucket: "test-bucket",
|
|
156
|
+
Policy: JSON.stringify(existingPolicy)
|
|
157
|
+
}, expect.any(Object));
|
|
158
|
+
expect(mockNavigate).toHaveBeenCalledWith("/buckets/test-bucket");
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
it("displays error message when save fails", async ()=>{
|
|
162
|
+
const noSuchPolicyError = new Error("Policy does not exist");
|
|
163
|
+
noSuchPolicyError.name = "NoSuchBucketPolicy";
|
|
164
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
165
|
+
data: void 0,
|
|
166
|
+
status: "error",
|
|
167
|
+
error: noSuchPolicyError
|
|
168
|
+
});
|
|
169
|
+
renderBucketPolicyPage();
|
|
170
|
+
await waitFor(()=>{
|
|
171
|
+
expect(screen.getByTestId("policy-editor")).toBeInTheDocument();
|
|
172
|
+
});
|
|
173
|
+
const editor = screen.getByTestId("policy-editor");
|
|
174
|
+
const validPolicy = JSON.stringify(existingPolicy, null, 2);
|
|
175
|
+
fireEvent.change(editor, {
|
|
176
|
+
target: {
|
|
177
|
+
value: validPolicy
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
const saveError = new Error("Access Denied");
|
|
181
|
+
mockMutate.mockImplementation((_, options)=>{
|
|
182
|
+
options?.onError?.(saveError);
|
|
183
|
+
});
|
|
184
|
+
const saveButton = screen.getByRole("button", {
|
|
185
|
+
name: /save/i
|
|
186
|
+
});
|
|
187
|
+
await waitFor(()=>{
|
|
188
|
+
expect(saveButton).not.toBeDisabled();
|
|
189
|
+
});
|
|
190
|
+
fireEvent.click(saveButton);
|
|
191
|
+
await waitFor(()=>{
|
|
192
|
+
expect(screen.getByText(/Save policy error: Access Denied/i)).toBeInTheDocument();
|
|
193
|
+
expect(mockNavigate).not.toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
it("handles cancel button click and navigates back", async ()=>{
|
|
197
|
+
const noSuchPolicyError = new Error("Policy does not exist");
|
|
198
|
+
noSuchPolicyError.name = "NoSuchBucketPolicy";
|
|
199
|
+
mockUseGetBucketPolicy.mockReturnValue({
|
|
200
|
+
data: void 0,
|
|
201
|
+
status: "error",
|
|
202
|
+
error: noSuchPolicyError
|
|
203
|
+
});
|
|
204
|
+
renderBucketPolicyPage();
|
|
205
|
+
await waitFor(()=>{
|
|
206
|
+
const cancelButton = screen.getByRole("button", {
|
|
207
|
+
name: /cancel/i
|
|
208
|
+
});
|
|
209
|
+
fireEvent.click(cancelButton);
|
|
210
|
+
});
|
|
211
|
+
expect(mockNavigate).toHaveBeenCalledWith("/buckets/test-bucket");
|
|
212
|
+
});
|
|
213
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|