@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,236 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
3
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
+ import { renderHook } from "@testing-library/react";
5
+ import "react";
6
+ import { DataBrowserProvider } from "../components/providers/DataBrowserProvider.js";
7
+ import { coreUIAvailableThemes } from "@scality/core-ui/dist/style/theme";
8
+ import { CoreUiThemeProvider } from "@scality/core-ui/dist/next";
9
+ import { ToastProvider } from "@scality/core-ui";
10
+ import { overrideHandlers, setupMswServer } from "./msw/index.js";
11
+ var __webpack_require__ = {};
12
+ (()=>{
13
+ __webpack_require__.g = (()=>{
14
+ if ('object' == typeof globalThis) return globalThis;
15
+ try {
16
+ return this || new Function('return this')();
17
+ } catch (e) {
18
+ if ('object' == typeof window) return window;
19
+ }
20
+ })();
21
+ })();
22
+ const testConfig = {
23
+ endpoint: "https://s3.amazonaws.com",
24
+ region: "us-east-1",
25
+ forcePathStyle: false
26
+ };
27
+ const testCredentials = {
28
+ accessKeyId: "test-access-key",
29
+ secretAccessKey: "test-secret-key",
30
+ sessionToken: "test-session-token"
31
+ };
32
+ const mockS3Client = {
33
+ send: jest.fn()
34
+ };
35
+ const MockedS3Client = S3Client;
36
+ const MockedPutObjectCommand = PutObjectCommand;
37
+ function mockOffsetSize(width, height) {
38
+ __webpack_require__.g.ResizeObserver = jest.fn().mockImplementation(()=>({
39
+ observe: jest.fn(),
40
+ unobserve: jest.fn(),
41
+ disconnect: jest.fn()
42
+ }));
43
+ Element.prototype.getBoundingClientRect = jest.fn(()=>({
44
+ width: width || 800,
45
+ height: height || 600,
46
+ top: 0,
47
+ left: 0,
48
+ bottom: height || 600,
49
+ right: width || 800,
50
+ x: 0,
51
+ y: 0,
52
+ toJSON: jest.fn()
53
+ }));
54
+ Object.defineProperties(window.HTMLElement.prototype, {
55
+ offsetHeight: {
56
+ get: ()=>height || 600,
57
+ configurable: true
58
+ },
59
+ offsetWidth: {
60
+ get: ()=>width || 800,
61
+ configurable: true
62
+ },
63
+ scrollHeight: {
64
+ get: ()=>height || 600,
65
+ configurable: true
66
+ },
67
+ scrollWidth: {
68
+ get: ()=>width || 800,
69
+ configurable: true
70
+ }
71
+ });
72
+ const mockComputedStyle = {
73
+ fontSize: "14px",
74
+ paddingLeft: "0px",
75
+ paddingRight: "0px",
76
+ paddingTop: "0px",
77
+ paddingBottom: "0px",
78
+ getPropertyValue: jest.fn((prop)=>{
79
+ const styles = {
80
+ "font-size": "14px",
81
+ "padding-left": "0px",
82
+ "padding-right": "0px",
83
+ "padding-top": "0px",
84
+ "padding-bottom": "0px"
85
+ };
86
+ return styles[prop] || "";
87
+ })
88
+ };
89
+ jest.spyOn(window, "getComputedStyle").mockImplementation(()=>mockComputedStyle);
90
+ }
91
+ const createQueryWrapper = ()=>{
92
+ const queryClient = new QueryClient({
93
+ defaultOptions: {
94
+ queries: {
95
+ retry: false
96
+ },
97
+ mutations: {
98
+ retry: false
99
+ }
100
+ }
101
+ });
102
+ const theme = coreUIAvailableThemes.darkRebrand;
103
+ return ({ children })=>/*#__PURE__*/ jsx(QueryClientProvider, {
104
+ client: queryClient,
105
+ children: /*#__PURE__*/ jsx(CoreUiThemeProvider, {
106
+ theme: theme,
107
+ children: children
108
+ })
109
+ });
110
+ };
111
+ const createTestWrapper = (config = testConfig, credentials = testCredentials)=>{
112
+ const queryClient = new QueryClient({
113
+ defaultOptions: {
114
+ queries: {
115
+ retry: false
116
+ },
117
+ mutations: {
118
+ retry: false
119
+ }
120
+ }
121
+ });
122
+ const getConfig = ()=>({
123
+ ...config,
124
+ credentials
125
+ });
126
+ const theme = coreUIAvailableThemes.darkRebrand;
127
+ return ({ children })=>/*#__PURE__*/ jsx(DataBrowserProvider, {
128
+ queryClient: queryClient,
129
+ getS3Config: getConfig,
130
+ theme: theme,
131
+ enableDevtools: false,
132
+ children: /*#__PURE__*/ jsx(CoreUiThemeProvider, {
133
+ theme: theme,
134
+ children: /*#__PURE__*/ jsx(ToastProvider, {
135
+ children: children
136
+ })
137
+ })
138
+ });
139
+ };
140
+ const renderHookWithWrapper = (hook, options)=>{
141
+ const { config, credentials, ...renderOptions } = options || {};
142
+ return renderHook(hook, {
143
+ wrapper: createTestWrapper(config, credentials),
144
+ ...renderOptions
145
+ });
146
+ };
147
+ const createTestFile = (name, content, type = "text/plain")=>new File([
148
+ content
149
+ ], name, {
150
+ type
151
+ });
152
+ const setupS3Mocks = ()=>{
153
+ jest.clearAllMocks();
154
+ MockedS3Client.mockImplementation(()=>mockS3Client);
155
+ MockedPutObjectCommand.mockImplementation((input)=>({
156
+ input,
157
+ resolveMiddleware: jest.fn()
158
+ }));
159
+ };
160
+ const setupCommonMocks = ()=>{
161
+ setupS3Mocks();
162
+ };
163
+ const overrideGlobalConfig = (overrides)=>{
164
+ const currentS3 = globalThis.__S3_CONFIG__;
165
+ const currentDev = globalThis.__DEV_CONFIG__;
166
+ if (overrides.s3) globalThis.__S3_CONFIG__ = {
167
+ ...currentS3,
168
+ ...overrides.s3
169
+ };
170
+ if (overrides.dev) globalThis.__DEV_CONFIG__ = {
171
+ ...currentDev,
172
+ ...overrides.dev
173
+ };
174
+ if (overrides.environment) {
175
+ if (void 0 !== overrides.environment.isDevelopment) globalThis.__IS_DEVELOPMENT__ = overrides.environment.isDevelopment;
176
+ if (void 0 !== overrides.environment.isProduction) globalThis.__IS_PRODUCTION__ = overrides.environment.isProduction;
177
+ }
178
+ };
179
+ const resetGlobalConfig = ()=>{
180
+ globalThis.__S3_CONFIG__ = {
181
+ endpoint: "http://localhost:8000",
182
+ region: "us-east-1",
183
+ realHost: "s3.amazonaws.com",
184
+ forcePathStyle: true
185
+ };
186
+ globalThis.__DEV_CONFIG__ = {
187
+ useProxy: false,
188
+ proxyEndpoint: "http://localhost:3000/api/s3",
189
+ proxyBasePath: "/api/s3",
190
+ proxyHost: "localhost",
191
+ proxyPort: 3000
192
+ };
193
+ globalThis.__IS_DEVELOPMENT__ = true;
194
+ globalThis.__IS_PRODUCTION__ = false;
195
+ };
196
+ const withGlobalConfig = (overrides, testFn)=>async ()=>{
197
+ overrideGlobalConfig(overrides);
198
+ try {
199
+ await testFn();
200
+ } finally{
201
+ resetGlobalConfig();
202
+ }
203
+ };
204
+ const validateFactoryHook = (hook, operationName)=>{
205
+ if ("function" != typeof hook) throw createFactoryTestError("Factory Validation", operationName, "hook should be a function - check if useCreate*Hook factory is properly returning a hook function");
206
+ };
207
+ const createFactoryTestError = (testContext, operationName, details)=>new Error(`Factory Test Failure [${testContext}]: ${operationName} - ${details || "Unknown error"}. Check factory implementation and hook usage patterns.`);
208
+ const validateHookResult = (result, hookType)=>{
209
+ const commonProps = [
210
+ "isError",
211
+ "isSuccess",
212
+ "data",
213
+ "error"
214
+ ];
215
+ const queryProps = [
216
+ "isLoading"
217
+ ];
218
+ const mutationProps = [
219
+ "mutate",
220
+ "mutateAsync",
221
+ "isPending"
222
+ ];
223
+ const infiniteProps = [
224
+ "hasNextPage",
225
+ "fetchNextPage",
226
+ "isFetchingNextPage"
227
+ ];
228
+ const expectedProps = [
229
+ ...commonProps,
230
+ ..."query" === hookType ? queryProps : [],
231
+ ..."mutation" === hookType ? mutationProps : [],
232
+ ..."infiniteQuery" === hookType ? infiniteProps : []
233
+ ];
234
+ for (const prop of expectedProps)if (!(prop in result)) throw createFactoryTestError("Hook Validation", hookType, `Missing property '${prop}' - check if factory is returning proper React Query hook result`);
235
+ };
236
+ export { MockedPutObjectCommand, MockedS3Client, createFactoryTestError, createQueryWrapper, createTestFile, createTestWrapper, mockOffsetSize, mockS3Client, overrideGlobalConfig, overrideHandlers, renderHookWithWrapper, resetGlobalConfig, setupCommonMocks, setupMswServer, setupS3Mocks, testConfig, testCredentials, validateFactoryHook, validateHookResult, withGlobalConfig };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,385 @@
1
+ import { EnhancedS3Error, ErrorCategory, createS3Error, createS3OperationError, isEnhancedS3Error, isS3ServiceException, shouldRetryError } from "../../utils/errorHandling.js";
2
+ const TEST_CONSTANTS = {
3
+ REQUEST_ID: "test-request-id",
4
+ ATTEMPTS: 1,
5
+ RETRY_DELAY: 0,
6
+ BUCKET: "test-bucket",
7
+ KEY: "test-key",
8
+ MESSAGES: {
9
+ NO_SUCH_BUCKET: "The specified bucket does not exist",
10
+ NO_SUCH_KEY: "The specified key does not exist",
11
+ INVALID_REQUEST: "Invalid request",
12
+ ACCESS_DENIED: "Access denied",
13
+ INTERNAL_ERROR: "Internal error",
14
+ NETWORK_FAILED: "Network connection failed",
15
+ SERVICE_UNAVAILABLE: "Service temporarily unavailable"
16
+ }
17
+ };
18
+ const createMockAWSError = (errorType, message, statusCode = 500)=>{
19
+ const error = new Error(message);
20
+ Object.defineProperty(error, "name", {
21
+ value: errorType,
22
+ writable: true
23
+ });
24
+ error.$metadata = {
25
+ httpStatusCode: statusCode,
26
+ requestId: TEST_CONSTANTS.REQUEST_ID,
27
+ attempts: TEST_CONSTANTS.ATTEMPTS,
28
+ totalRetryDelay: TEST_CONSTANTS.RETRY_DELAY
29
+ };
30
+ return error;
31
+ };
32
+ const TestErrors = {
33
+ noSuchBucket: ()=>createMockAWSError("NoSuchBucket", TEST_CONSTANTS.MESSAGES.NO_SUCH_BUCKET, 404),
34
+ noSuchKey: ()=>createMockAWSError("NoSuchKey", TEST_CONSTANTS.MESSAGES.NO_SUCH_KEY, 404),
35
+ invalidRequest: ()=>createMockAWSError("InvalidRequest", TEST_CONSTANTS.MESSAGES.INVALID_REQUEST, 400),
36
+ accessDenied: ()=>createMockAWSError("AccessDenied", TEST_CONSTANTS.MESSAGES.ACCESS_DENIED, 403),
37
+ internalError: ()=>createMockAWSError("InternalError", TEST_CONSTANTS.MESSAGES.INTERNAL_ERROR, 500),
38
+ serviceUnavailable: ()=>createMockAWSError("ServiceUnavailable", TEST_CONSTANTS.MESSAGES.SERVICE_UNAVAILABLE, 503),
39
+ abortError: ()=>{
40
+ const error = new Error("Operation aborted");
41
+ Object.defineProperty(error, "name", {
42
+ value: "AbortError",
43
+ writable: true
44
+ });
45
+ return error;
46
+ },
47
+ networkError: ()=>new Error(TEST_CONSTANTS.MESSAGES.NETWORK_FAILED),
48
+ timeoutError: ()=>{
49
+ const error = new Error("Request timeout");
50
+ Object.defineProperty(error, "name", {
51
+ value: "TimeoutError",
52
+ writable: true
53
+ });
54
+ return error;
55
+ }
56
+ };
57
+ const TestEnhancedErrors = {
58
+ server: ()=>new EnhancedS3Error("Server error", "ServerError", ErrorCategory.SERVER_ERROR, new Error()),
59
+ network: ()=>new EnhancedS3Error("Network error", "NetworkError", ErrorCategory.NETWORK_ERROR, new Error()),
60
+ client: ()=>new EnhancedS3Error("Client error", "ClientError", ErrorCategory.CLIENT_ERROR, new Error()),
61
+ auth: ()=>new EnhancedS3Error("Auth error", "AuthError", ErrorCategory.AUTHORIZATION, new Error()),
62
+ notFound: ()=>new EnhancedS3Error("Not found", "NotFoundError", ErrorCategory.NOT_FOUND, new Error()),
63
+ cancelled: ()=>new EnhancedS3Error("Cancelled", "CancelError", ErrorCategory.CANCELLATION, new Error()),
64
+ unknown: ()=>new EnhancedS3Error("Unknown", "UnknownError", ErrorCategory.UNKNOWN, new Error())
65
+ };
66
+ describe("ErrorCategory", ()=>{
67
+ test("contains all expected categories", ()=>{
68
+ expect(ErrorCategory.CLIENT_ERROR).toBe("CLIENT_ERROR");
69
+ expect(ErrorCategory.SERVER_ERROR).toBe("SERVER_ERROR");
70
+ expect(ErrorCategory.NETWORK_ERROR).toBe("NETWORK_ERROR");
71
+ expect(ErrorCategory.CANCELLATION).toBe("CANCELLATION");
72
+ expect(ErrorCategory.AUTHORIZATION).toBe("AUTHORIZATION");
73
+ expect(ErrorCategory.NOT_FOUND).toBe("NOT_FOUND");
74
+ expect(ErrorCategory.UNKNOWN).toBe("UNKNOWN");
75
+ });
76
+ });
77
+ describe("EnhancedS3Error", ()=>{
78
+ test("creates error with all properties", ()=>{
79
+ const originalError = new Error("Original error");
80
+ const metadata = {
81
+ httpStatusCode: 400,
82
+ requestId: TEST_CONSTANTS.REQUEST_ID,
83
+ attempts: TEST_CONSTANTS.ATTEMPTS,
84
+ totalRetryDelay: TEST_CONSTANTS.RETRY_DELAY
85
+ };
86
+ const context = {
87
+ operation: "test",
88
+ bucket: TEST_CONSTANTS.BUCKET
89
+ };
90
+ const error = new EnhancedS3Error("Test error", "TestError", ErrorCategory.CLIENT_ERROR, originalError, 400, metadata, context);
91
+ expect(error.message).toBe("Test error");
92
+ expect(error.name).toBe("TestError");
93
+ expect(error.category).toBe(ErrorCategory.CLIENT_ERROR);
94
+ expect(error.statusCode).toBe(400);
95
+ expect(error.metadata).toBe(metadata);
96
+ expect(error.originalError).toBe(originalError);
97
+ expect(error.context).toBe(context);
98
+ expect(error instanceof Error).toBe(true);
99
+ expect(error instanceof EnhancedS3Error).toBe(true);
100
+ });
101
+ describe("shouldRetry", ()=>{
102
+ test("returns true for retryable categories", ()=>{
103
+ expect(TestEnhancedErrors.server().shouldRetry()).toBe(true);
104
+ expect(TestEnhancedErrors.network().shouldRetry()).toBe(true);
105
+ });
106
+ test("returns false for non-retryable categories", ()=>{
107
+ expect(TestEnhancedErrors.client().shouldRetry()).toBe(false);
108
+ expect(TestEnhancedErrors.auth().shouldRetry()).toBe(false);
109
+ expect(TestEnhancedErrors.notFound().shouldRetry()).toBe(false);
110
+ expect(TestEnhancedErrors.cancelled().shouldRetry()).toBe(false);
111
+ expect(TestEnhancedErrors.unknown().shouldRetry()).toBe(false);
112
+ });
113
+ });
114
+ });
115
+ describe("Type Guards", ()=>{
116
+ describe("isS3ServiceException", ()=>{
117
+ test("returns true for S3ServiceException", ()=>{
118
+ expect(isS3ServiceException(TestErrors.invalidRequest())).toBe(true);
119
+ });
120
+ test("returns false for regular Error", ()=>{
121
+ expect(isS3ServiceException(new Error("Regular error"))).toBe(false);
122
+ });
123
+ test("returns false for non-Error objects", ()=>{
124
+ expect(isS3ServiceException("string")).toBe(false);
125
+ expect(isS3ServiceException(null)).toBe(false);
126
+ expect(isS3ServiceException(void 0)).toBe(false);
127
+ expect(isS3ServiceException({})).toBe(false);
128
+ });
129
+ });
130
+ describe("isEnhancedS3Error", ()=>{
131
+ test("returns true for EnhancedS3Error", ()=>{
132
+ expect(isEnhancedS3Error(TestEnhancedErrors.unknown())).toBe(true);
133
+ });
134
+ test("returns false for other error types", ()=>{
135
+ expect(isEnhancedS3Error(new Error("Regular error"))).toBe(false);
136
+ expect(isEnhancedS3Error(TestErrors.invalidRequest())).toBe(false);
137
+ expect(isEnhancedS3Error("string")).toBe(false);
138
+ });
139
+ });
140
+ });
141
+ describe("createS3Error", ()=>{
142
+ test("handles AbortError", ()=>{
143
+ const abortError = TestErrors.abortError();
144
+ const result = createS3Error(abortError);
145
+ expect(result.message).toBe("Operation was cancelled");
146
+ expect(result.name).toBe("AbortError");
147
+ expect(result.category).toBe(ErrorCategory.CANCELLATION);
148
+ expect(result.originalError).toBe(abortError);
149
+ });
150
+ test("handles specific AWS errors", ()=>{
151
+ const testCases = [
152
+ {
153
+ error: TestErrors.noSuchBucket(),
154
+ expectedCategory: ErrorCategory.NOT_FOUND,
155
+ expectedName: "NoSuchBucket",
156
+ expectedStatus: 404
157
+ },
158
+ {
159
+ error: TestErrors.noSuchKey(),
160
+ expectedCategory: ErrorCategory.NOT_FOUND,
161
+ expectedName: "NoSuchKey",
162
+ expectedStatus: 404
163
+ },
164
+ {
165
+ error: TestErrors.invalidRequest(),
166
+ expectedCategory: ErrorCategory.CLIENT_ERROR,
167
+ expectedName: "InvalidRequest",
168
+ expectedStatus: 400
169
+ }
170
+ ];
171
+ testCases.forEach(({ error, expectedCategory, expectedName, expectedStatus })=>{
172
+ const result = createS3Error(error);
173
+ expect(result.category).toBe(expectedCategory);
174
+ expect(result.name).toBe(expectedName);
175
+ expect(result.statusCode).toBe(expectedStatus);
176
+ expect(result.metadata).toBe(error.$metadata);
177
+ });
178
+ });
179
+ test("handles status code classification", ()=>{
180
+ const testCases = [
181
+ {
182
+ error: TestErrors.accessDenied(),
183
+ expectedCategory: ErrorCategory.AUTHORIZATION
184
+ },
185
+ {
186
+ error: TestErrors.internalError(),
187
+ expectedCategory: ErrorCategory.SERVER_ERROR
188
+ }
189
+ ];
190
+ testCases.forEach(({ error, expectedCategory })=>{
191
+ const result = createS3Error(error);
192
+ expect(result.category).toBe(expectedCategory);
193
+ });
194
+ });
195
+ test("handles network errors", ()=>{
196
+ const testCases = [
197
+ TestErrors.networkError(),
198
+ TestErrors.timeoutError()
199
+ ];
200
+ testCases.forEach((error)=>{
201
+ const result = createS3Error(error);
202
+ expect(result.category).toBe(ErrorCategory.NETWORK_ERROR);
203
+ });
204
+ });
205
+ test("handles edge cases", ()=>{
206
+ const testCases = [
207
+ {
208
+ input: new Error("Some error"),
209
+ expectedCategory: ErrorCategory.UNKNOWN,
210
+ expectedMessage: "Some error"
211
+ },
212
+ {
213
+ input: "String error",
214
+ expectedCategory: ErrorCategory.UNKNOWN,
215
+ expectedMessage: "String error",
216
+ expectedName: "UnknownError"
217
+ },
218
+ {
219
+ input: null,
220
+ expectedCategory: ErrorCategory.UNKNOWN,
221
+ expectedMessage: "Unknown error occurred"
222
+ },
223
+ {
224
+ input: void 0,
225
+ expectedCategory: ErrorCategory.UNKNOWN,
226
+ expectedMessage: "Unknown error occurred"
227
+ }
228
+ ];
229
+ testCases.forEach(({ input, expectedCategory, expectedMessage, expectedName })=>{
230
+ const result = createS3Error(input);
231
+ expect(result.category).toBe(expectedCategory);
232
+ expect(result.message).toBe(expectedMessage);
233
+ if (expectedName) expect(result.name).toBe(expectedName);
234
+ });
235
+ });
236
+ test("preserves context", ()=>{
237
+ const context = {
238
+ operation: "test",
239
+ bucket: TEST_CONSTANTS.BUCKET
240
+ };
241
+ const result = createS3Error(new Error("Test error"), context);
242
+ expect(result.context).toBe(context);
243
+ });
244
+ });
245
+ describe("createS3OperationError", ()=>{
246
+ test("message modification scenarios", ()=>{
247
+ const testCases = [
248
+ {
249
+ name: "preserves existing operation in message",
250
+ error: new Error("ListObjectsV2 failed: Access denied"),
251
+ operation: "ListObjectsV2",
252
+ bucket: TEST_CONSTANTS.BUCKET,
253
+ expectedMessage: "ListObjectsV2 failed: Access denied"
254
+ },
255
+ {
256
+ name: "enhances message with operation context",
257
+ error: new Error(TEST_CONSTANTS.MESSAGES.ACCESS_DENIED),
258
+ operation: "GetObject",
259
+ bucket: TEST_CONSTANTS.BUCKET,
260
+ key: TEST_CONSTANTS.KEY,
261
+ expectedMessage: `GetObject failed for bucket '${TEST_CONSTANTS.BUCKET}', key '${TEST_CONSTANTS.KEY}': ${TEST_CONSTANTS.MESSAGES.ACCESS_DENIED}`
262
+ },
263
+ {
264
+ name: "handles operation without bucket",
265
+ error: new Error(TEST_CONSTANTS.MESSAGES.SERVICE_UNAVAILABLE),
266
+ operation: "ListBuckets",
267
+ expectedMessage: `ListBuckets failed: ${TEST_CONSTANTS.MESSAGES.SERVICE_UNAVAILABLE}`
268
+ }
269
+ ];
270
+ testCases.forEach(({ error, operation, bucket, key, expectedMessage })=>{
271
+ const result = createS3OperationError(error, operation, bucket, key);
272
+ expect(result.message).toBe(expectedMessage);
273
+ expect(result.context).toEqual({
274
+ operation,
275
+ bucketName: bucket || void 0,
276
+ objectKey: key || void 0,
277
+ timestamp: expect.any(String)
278
+ });
279
+ });
280
+ });
281
+ test("preserves all error properties", ()=>{
282
+ const originalError = TestErrors.noSuchBucket();
283
+ const result = createS3OperationError(originalError, "HeadObject", TEST_CONSTANTS.BUCKET, TEST_CONSTANTS.KEY);
284
+ expect(result.category).toBe(ErrorCategory.NOT_FOUND);
285
+ expect(result.statusCode).toBe(404);
286
+ expect(result.metadata).toBe(originalError.$metadata);
287
+ expect(result.originalError).toBe(originalError);
288
+ });
289
+ });
290
+ describe("shouldRetryError", ()=>{
291
+ test("respects maxRetries limit", ()=>{
292
+ const retryableError = TestEnhancedErrors.server();
293
+ const maxRetries = 3;
294
+ expect(shouldRetryError(retryableError, 0, maxRetries)).toBe(true);
295
+ expect(shouldRetryError(retryableError, 2, maxRetries)).toBe(true);
296
+ expect(shouldRetryError(retryableError, 3, maxRetries)).toBe(false);
297
+ expect(shouldRetryError(retryableError, 5, maxRetries)).toBe(false);
298
+ });
299
+ test("uses error category for retry decision", ()=>{
300
+ const testCases = [
301
+ {
302
+ error: TestEnhancedErrors.server(),
303
+ shouldRetry: true
304
+ },
305
+ {
306
+ error: TestEnhancedErrors.network(),
307
+ shouldRetry: true
308
+ },
309
+ {
310
+ error: TestEnhancedErrors.client(),
311
+ shouldRetry: false
312
+ },
313
+ {
314
+ error: TestEnhancedErrors.auth(),
315
+ shouldRetry: false
316
+ }
317
+ ];
318
+ testCases.forEach(({ error, shouldRetry })=>{
319
+ expect(shouldRetryError(error, 0)).toBe(shouldRetry);
320
+ });
321
+ });
322
+ test("converts and retries AWS errors", ()=>{
323
+ const testCases = [
324
+ {
325
+ error: TestErrors.internalError(),
326
+ shouldRetry: true
327
+ },
328
+ {
329
+ error: TestErrors.invalidRequest(),
330
+ shouldRetry: false
331
+ }
332
+ ];
333
+ testCases.forEach(({ error, shouldRetry })=>{
334
+ expect(shouldRetryError(error, 0)).toBe(shouldRetry);
335
+ });
336
+ });
337
+ test("uses default maxRetries of 3", ()=>{
338
+ const retryableError = TestEnhancedErrors.server();
339
+ expect(shouldRetryError(retryableError, 0)).toBe(true);
340
+ expect(shouldRetryError(retryableError, 3)).toBe(false);
341
+ });
342
+ });
343
+ describe("Integration Tests", ()=>{
344
+ test("complete error processing workflow", ()=>{
345
+ const testCases = [
346
+ {
347
+ name: "NoSuchBucket - non-retryable",
348
+ originalError: TestErrors.noSuchBucket(),
349
+ operation: "ListObjectsV2",
350
+ bucket: TEST_CONSTANTS.BUCKET,
351
+ expectedCategory: ErrorCategory.NOT_FOUND,
352
+ shouldRetry: false
353
+ },
354
+ {
355
+ name: "Server error - retryable",
356
+ originalError: TestErrors.serviceUnavailable(),
357
+ operation: "PutObject",
358
+ bucket: TEST_CONSTANTS.BUCKET,
359
+ key: TEST_CONSTANTS.KEY,
360
+ expectedCategory: ErrorCategory.SERVER_ERROR,
361
+ shouldRetry: true
362
+ }
363
+ ];
364
+ testCases.forEach(({ originalError, operation, bucket, key, expectedCategory, shouldRetry })=>{
365
+ const operationError = createS3OperationError(originalError, operation, bucket, key);
366
+ expect(operationError.category).toBe(expectedCategory);
367
+ expect(operationError.shouldRetry()).toBe(shouldRetry);
368
+ expect(isEnhancedS3Error(operationError)).toBe(true);
369
+ expect(shouldRetryError(operationError, 0)).toBe(shouldRetry);
370
+ expect(shouldRetryError(operationError, 3)).toBe(false);
371
+ });
372
+ });
373
+ test("retry attempt progression", ()=>{
374
+ const serverError = TestErrors.serviceUnavailable();
375
+ const operationError = createS3OperationError(serverError, "PutObject", TEST_CONSTANTS.BUCKET, TEST_CONSTANTS.KEY);
376
+ [
377
+ 0,
378
+ 1,
379
+ 2
380
+ ].forEach((attempt)=>{
381
+ expect(shouldRetryError(operationError, attempt)).toBe(true);
382
+ });
383
+ expect(shouldRetryError(operationError, 3)).toBe(false);
384
+ });
385
+ });
@@ -0,0 +1,48 @@
1
+ import { S3ClientConfig } from "@aws-sdk/client-s3";
2
+ import type { AwsCredentialIdentity } from "@aws-sdk/types";
3
+ export interface S3BrowserConfig extends Omit<S3ClientConfig, "credentials"> {
4
+ endpoint?: string;
5
+ region: string;
6
+ forcePathStyle?: boolean;
7
+ useDevProxy?: boolean;
8
+ realS3Host?: string;
9
+ proxyPath?: string;
10
+ publicAclIndicator?: string;
11
+ }
12
+ export interface S3Credentials extends AwsCredentialIdentity {
13
+ roleArn?: string;
14
+ features?: string[];
15
+ }
16
+ export type GetConfigFunction = () => S3BrowserConfig & {
17
+ credentials: S3Credentials;
18
+ };
19
+ export interface ObjectInfo {
20
+ key: string;
21
+ size?: number;
22
+ lastModified?: Date;
23
+ etag?: string;
24
+ storageClass?: string;
25
+ owner?: {
26
+ displayName?: string;
27
+ id?: string;
28
+ };
29
+ }
30
+ export interface ObjectVersion extends ObjectInfo {
31
+ versionId: string;
32
+ isLatest: boolean;
33
+ isDeleteMarker?: boolean;
34
+ }
35
+ export interface CommonPrefix {
36
+ prefix: string;
37
+ }
38
+ export interface ListObjectsResult {
39
+ objects: ObjectInfo[];
40
+ commonPrefixes: CommonPrefix[];
41
+ nextContinuationToken?: string;
42
+ isTruncated: boolean;
43
+ }
44
+ export type S3ErrorCode = "AccessDenied" | "NoSuchBucket" | "NoSuchKey" | "InvalidBucketName" | "BucketNotFound" | "NetworkError" | "UnknownError";
45
+ export interface S3Error extends Error {
46
+ code?: S3ErrorCode;
47
+ statusCode?: number;
48
+ }
File without changes
@@ -0,0 +1,2 @@
1
+ export * from "./types";
2
+ export * from "./messages";
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./messages.js";
@@ -0,0 +1,5 @@
1
+ import { DeletionContext, DeletionResult } from "./types";
2
+ /**
3
+ * Determines deletion behavior based on bucket versioning
4
+ */
5
+ export declare const getDeletionMessages: (context: DeletionContext) => DeletionResult;