@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.
Files changed (176) hide show
  1. package/dist/components/Editor.d.ts +12 -0
  2. package/dist/components/Editor.js +28 -0
  3. package/dist/components/__tests__/BucketList.test.d.ts +1 -0
  4. package/dist/components/__tests__/BucketList.test.js +225 -0
  5. package/dist/components/__tests__/BucketOverview.test.d.ts +1 -0
  6. package/dist/components/__tests__/BucketOverview.test.js +479 -0
  7. package/dist/components/__tests__/BucketPolicyPage.test.d.ts +1 -0
  8. package/dist/components/__tests__/BucketPolicyPage.test.js +213 -0
  9. package/dist/components/__tests__/CreateFolderButton.test.d.ts +1 -0
  10. package/dist/components/__tests__/CreateFolderButton.test.js +147 -0
  11. package/dist/components/__tests__/DeleteBucketButton.test.d.ts +1 -0
  12. package/dist/components/__tests__/DeleteBucketButton.test.js +272 -0
  13. package/dist/components/__tests__/DeleteObjectButton.test.d.ts +1 -0
  14. package/dist/components/__tests__/DeleteObjectButton.test.js +302 -0
  15. package/dist/components/__tests__/MetadataSearch.test.d.ts +1 -0
  16. package/dist/components/__tests__/MetadataSearch.test.js +201 -0
  17. package/dist/components/__tests__/ObjectList.test.d.ts +1 -0
  18. package/dist/components/__tests__/ObjectList.test.js +283 -0
  19. package/dist/components/__tests__/UploadButton.test.d.ts +1 -0
  20. package/dist/components/__tests__/UploadButton.test.js +144 -0
  21. package/dist/components/buckets/BucketDetails.d.ts +1 -0
  22. package/dist/components/buckets/BucketDetails.js +51 -0
  23. package/dist/components/buckets/BucketList.d.ts +12 -0
  24. package/dist/components/buckets/BucketList.js +136 -0
  25. package/dist/components/buckets/BucketLocation.d.ts +3 -0
  26. package/dist/components/buckets/BucketLocation.js +16 -0
  27. package/dist/components/buckets/BucketOverview.d.ts +14 -0
  28. package/dist/components/buckets/BucketOverview.js +209 -0
  29. package/dist/components/buckets/BucketPage.d.ts +2 -0
  30. package/dist/components/buckets/BucketPage.js +47 -0
  31. package/dist/components/buckets/BucketPolicyButton.d.ts +7 -0
  32. package/dist/components/buckets/BucketPolicyButton.js +18 -0
  33. package/dist/components/buckets/BucketPolicyPage.d.ts +1 -0
  34. package/dist/components/buckets/BucketPolicyPage.js +205 -0
  35. package/dist/components/buckets/DeleteBucketButton.d.ts +8 -0
  36. package/dist/components/buckets/DeleteBucketButton.js +78 -0
  37. package/dist/components/index.d.ts +12 -0
  38. package/dist/components/index.js +13 -0
  39. package/dist/components/layouts/BrowserPageLayout.d.ts +9 -0
  40. package/dist/components/layouts/BrowserPageLayout.js +46 -0
  41. package/dist/components/objects/CreateFolderButton.d.ts +29 -0
  42. package/dist/components/objects/CreateFolderButton.js +118 -0
  43. package/dist/components/objects/DeleteObjectButton.d.ts +8 -0
  44. package/dist/components/objects/DeleteObjectButton.js +191 -0
  45. package/dist/components/objects/ObjectDetails/ObjectMetadata.d.ts +2 -0
  46. package/dist/components/objects/ObjectDetails/ObjectMetadata.js +323 -0
  47. package/dist/components/objects/ObjectDetails/ObjectSummary.d.ts +3 -0
  48. package/dist/components/objects/ObjectDetails/ObjectSummary.js +193 -0
  49. package/dist/components/objects/ObjectDetails/ObjectTags.d.ts +3 -0
  50. package/dist/components/objects/ObjectDetails/ObjectTags.js +300 -0
  51. package/dist/components/objects/ObjectDetails/index.d.ts +9 -0
  52. package/dist/components/objects/ObjectDetails/index.js +49 -0
  53. package/dist/components/objects/ObjectList.d.ts +40 -0
  54. package/dist/components/objects/ObjectList.js +407 -0
  55. package/dist/components/objects/ObjectPage.d.ts +1 -0
  56. package/dist/components/objects/ObjectPage.js +43 -0
  57. package/dist/components/objects/UploadButton.d.ts +34 -0
  58. package/dist/components/objects/UploadButton.js +229 -0
  59. package/dist/components/providers/DataBrowserProvider.d.ts +20 -0
  60. package/dist/components/providers/DataBrowserProvider.js +42 -0
  61. package/dist/components/search/MetadataSearch.d.ts +5 -0
  62. package/dist/components/search/MetadataSearch.js +162 -0
  63. package/dist/components/search/SearchHints.d.ts +8 -0
  64. package/dist/components/search/SearchHints.js +21 -0
  65. package/dist/components/ui/DeleteObjectModalContent.d.ts +5 -0
  66. package/dist/components/ui/DeleteObjectModalContent.js +71 -0
  67. package/dist/components/ui/Search.elements.d.ts +17 -0
  68. package/dist/components/ui/Search.elements.js +59 -0
  69. package/dist/components/ui/Table.elements.d.ts +36 -0
  70. package/dist/components/ui/Table.elements.js +87 -0
  71. package/dist/config/factory.d.ts +52 -0
  72. package/dist/config/factory.js +70 -0
  73. package/dist/config/types.d.ts +46 -0
  74. package/dist/config/types.js +0 -0
  75. package/dist/hooks/__tests__/useIsBucketEmpty.test.d.ts +1 -0
  76. package/dist/hooks/__tests__/useIsBucketEmpty.test.js +122 -0
  77. package/dist/hooks/bucketConfiguration.d.ts +147 -0
  78. package/dist/hooks/bucketConfiguration.js +59 -0
  79. package/dist/hooks/bucketOperations.d.ts +36 -0
  80. package/dist/hooks/bucketOperations.js +12 -0
  81. package/dist/hooks/factories/__tests__/useCreateS3FunctionMutationHook.test.d.ts +1 -0
  82. package/dist/hooks/factories/__tests__/useCreateS3FunctionMutationHook.test.js +276 -0
  83. package/dist/hooks/factories/__tests__/useCreateS3InfiniteQueryHook.test.d.ts +1 -0
  84. package/dist/hooks/factories/__tests__/useCreateS3InfiniteQueryHook.test.js +259 -0
  85. package/dist/hooks/factories/__tests__/useCreateS3LoginHook.test.d.ts +1 -0
  86. package/dist/hooks/factories/__tests__/useCreateS3LoginHook.test.js +166 -0
  87. package/dist/hooks/factories/__tests__/useCreateS3MutationHook.test.d.ts +1 -0
  88. package/dist/hooks/factories/__tests__/useCreateS3MutationHook.test.js +200 -0
  89. package/dist/hooks/factories/__tests__/useCreateS3QueryHook.test.d.ts +1 -0
  90. package/dist/hooks/factories/__tests__/useCreateS3QueryHook.test.js +136 -0
  91. package/dist/hooks/factories/index.d.ts +18 -0
  92. package/dist/hooks/factories/index.js +5 -0
  93. package/dist/hooks/factories/useCreateS3InfiniteQueryHook.d.ts +13 -0
  94. package/dist/hooks/factories/useCreateS3InfiniteQueryHook.js +76 -0
  95. package/dist/hooks/factories/useCreateS3LoginHook.d.ts +8 -0
  96. package/dist/hooks/factories/useCreateS3LoginHook.js +22 -0
  97. package/dist/hooks/factories/useCreateS3MutationHook.d.ts +5 -0
  98. package/dist/hooks/factories/useCreateS3MutationHook.js +50 -0
  99. package/dist/hooks/factories/useCreateS3QueryHook.d.ts +3 -0
  100. package/dist/hooks/factories/useCreateS3QueryHook.js +30 -0
  101. package/dist/hooks/index.d.ts +8 -0
  102. package/dist/hooks/index.js +8 -0
  103. package/dist/hooks/loginOperations.d.ts +21 -0
  104. package/dist/hooks/loginOperations.js +9 -0
  105. package/dist/hooks/objectOperations.d.ts +190 -0
  106. package/dist/hooks/objectOperations.js +66 -0
  107. package/dist/hooks/presignedOperations.d.ts +73 -0
  108. package/dist/hooks/presignedOperations.js +72 -0
  109. package/dist/hooks/useIsBucketEmpty.d.ts +7 -0
  110. package/dist/hooks/useIsBucketEmpty.js +36 -0
  111. package/dist/hooks/useLoginMutation.d.ts +21 -0
  112. package/dist/hooks/useLoginMutation.js +9 -0
  113. package/dist/hooks/useS3Client.d.ts +1 -0
  114. package/dist/hooks/useS3Client.js +13 -0
  115. package/dist/index.d.ts +6 -0
  116. package/dist/index.js +6 -0
  117. package/dist/schemas/bucketPolicySchema.json +321 -0
  118. package/dist/test/msw/handlers/deleteBucket.d.ts +1 -0
  119. package/dist/test/msw/handlers/deleteBucket.js +14 -0
  120. package/dist/test/msw/handlers/getBucketAcl.d.ts +1 -0
  121. package/dist/test/msw/handlers/getBucketAcl.js +96 -0
  122. package/dist/test/msw/handlers/getBucketLocation.d.ts +1 -0
  123. package/dist/test/msw/handlers/getBucketLocation.js +23 -0
  124. package/dist/test/msw/handlers/getBucketPolicy.d.ts +11 -0
  125. package/dist/test/msw/handlers/getBucketPolicy.js +72 -0
  126. package/dist/test/msw/handlers/headObject.d.ts +1 -0
  127. package/dist/test/msw/handlers/headObject.js +17 -0
  128. package/dist/test/msw/handlers/listBuckets.d.ts +1 -0
  129. package/dist/test/msw/handlers/listBuckets.js +24 -0
  130. package/dist/test/msw/handlers/listObjectVersions.d.ts +1 -0
  131. package/dist/test/msw/handlers/listObjectVersions.js +83 -0
  132. package/dist/test/msw/handlers/listObjects.d.ts +1 -0
  133. package/dist/test/msw/handlers/listObjects.js +66 -0
  134. package/dist/test/msw/handlers/objectLegalHold.d.ts +1 -0
  135. package/dist/test/msw/handlers/objectLegalHold.js +24 -0
  136. package/dist/test/msw/handlers/objectRetention.d.ts +1 -0
  137. package/dist/test/msw/handlers/objectRetention.js +27 -0
  138. package/dist/test/msw/handlers/putBucketAcl.d.ts +1 -0
  139. package/dist/test/msw/handlers/putBucketAcl.js +18 -0
  140. package/dist/test/msw/handlers/putObject.d.ts +1 -0
  141. package/dist/test/msw/handlers/putObject.js +16 -0
  142. package/dist/test/msw/handlers.d.ts +4 -0
  143. package/dist/test/msw/handlers.js +109 -0
  144. package/dist/test/msw/index.d.ts +2 -0
  145. package/dist/test/msw/index.js +3 -0
  146. package/dist/test/msw/server.d.ts +4 -0
  147. package/dist/test/msw/server.js +20 -0
  148. package/dist/test/msw/utils.d.ts +2 -0
  149. package/dist/test/msw/utils.js +13 -0
  150. package/dist/test/setup.d.ts +1 -0
  151. package/dist/test/setup.js +82 -0
  152. package/dist/test/testUtils.d.ts +82 -0
  153. package/dist/test/testUtils.js +236 -0
  154. package/dist/test/utils/errorHandling.test.d.ts +1 -0
  155. package/dist/test/utils/errorHandling.test.js +385 -0
  156. package/dist/types/index.d.ts +48 -0
  157. package/dist/types/index.js +0 -0
  158. package/dist/utils/deletion/index.d.ts +2 -0
  159. package/dist/utils/deletion/index.js +2 -0
  160. package/dist/utils/deletion/messages.d.ts +5 -0
  161. package/dist/utils/deletion/messages.js +29 -0
  162. package/dist/utils/deletion/types.d.ts +11 -0
  163. package/dist/utils/deletion/types.js +0 -0
  164. package/dist/utils/errorHandling.d.ts +54 -0
  165. package/dist/utils/errorHandling.js +79 -0
  166. package/dist/utils/hooks.d.ts +2 -0
  167. package/dist/utils/hooks.js +26 -0
  168. package/dist/utils/index.d.ts +2 -0
  169. package/dist/utils/index.js +2 -0
  170. package/dist/utils/proxyMiddleware.d.ts +18 -0
  171. package/dist/utils/proxyMiddleware.js +56 -0
  172. package/dist/utils/s3Client.d.ts +5 -0
  173. package/dist/utils/s3Client.js +35 -0
  174. package/dist/utils/useFeatures.d.ts +1 -0
  175. package/dist/utils/useFeatures.js +7 -0
  176. 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,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
+ });