@geekmidas/storage 0.0.1

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/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@geekmidas/storage",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org/",
7
+ "access": "public"
8
+ },
9
+ "type": "module",
10
+ "exports": {
11
+ "./aws": {
12
+ "import": "./dist/aws.mjs",
13
+ "require": "./dist/aws.cjs",
14
+ "types": "./src/aws.ts"
15
+ },
16
+ ".": {
17
+ "import": "./dist/index.mjs",
18
+ "require": "./dist/index.cjs",
19
+ "types": "./src/index.ts"
20
+ }
21
+ },
22
+ "dependencies": {
23
+ "zod": "~3.25.67"
24
+ },
25
+ "peerDependencies": {
26
+ "@aws-sdk/client-s3": "~3.844.0",
27
+ "@aws-sdk/s3-presigned-post": "^3.844.0",
28
+ "@aws-sdk/s3-request-presigner": "~3.844.0"
29
+ },
30
+ "scripts": {}
31
+ }
@@ -0,0 +1,176 @@
1
+ import {
2
+ GetObjectCommand,
3
+ ListObjectVersionsCommand,
4
+ PutObjectCommand,
5
+ S3Client,
6
+ } from '@aws-sdk/client-s3';
7
+ import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
8
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
9
+
10
+ import {
11
+ type DocumentVersion,
12
+ type File,
13
+ type GetUploadParams,
14
+ type GetUploadResponse,
15
+ type StorageClient,
16
+ StorageProvider,
17
+ } from './StorageClient';
18
+
19
+ export class AmazonStorageClient implements StorageClient {
20
+ readonly provider = StorageProvider.AWSS3;
21
+ static create(
22
+ options: AmazonStorageClientCreateOptions,
23
+ ): AmazonStorageClient {
24
+ const {
25
+ bucket,
26
+ region,
27
+ accessKeyId,
28
+ acl,
29
+ endpoint,
30
+ secretAccessKey,
31
+ forcePathStyle = false,
32
+ } = options;
33
+ const hasCredentials = accessKeyId && secretAccessKey;
34
+ const credentials = hasCredentials
35
+ ? { accessKeyId, secretAccessKey }
36
+ : undefined;
37
+
38
+ const client = new S3Client({
39
+ region,
40
+ credentials,
41
+ endpoint,
42
+ forcePathStyle,
43
+ });
44
+
45
+ return new AmazonStorageClient(client, bucket, acl);
46
+ }
47
+ constructor(
48
+ private readonly client: S3Client,
49
+ private readonly bucket: string,
50
+ private readonly acl = AmazonCannedAccessControlList.AuthenticatedRead,
51
+ ) {}
52
+
53
+ getVersionDownloadURL(file: File, versionId: string): Promise<string> {
54
+ const ResponseContentDisposition = file.name
55
+ ? `attachment; filename=${encodeURIComponent(file.name)}`
56
+ : undefined;
57
+
58
+ const command = new GetObjectCommand({
59
+ Bucket: this.bucket,
60
+ Key: file.path,
61
+ ResponseContentDisposition,
62
+ VersionId: versionId,
63
+ });
64
+
65
+ return getSignedUrl(this.client, command, { expiresIn: 60 * 60 * 24 });
66
+ }
67
+
68
+ async getVersions(key: string): Promise<DocumentVersion[]> {
69
+ const command = new ListObjectVersionsCommand({
70
+ Bucket: this.bucket,
71
+ Prefix: key,
72
+ });
73
+
74
+ const { Versions = [] } = await this.client.send(command);
75
+
76
+ return Versions.map((version) => ({
77
+ id: version.VersionId || '',
78
+ createdAt: version.LastModified || new Date(),
79
+ }));
80
+ }
81
+
82
+ getDownloadURL(file: File, expiresIn = 60 * 60): Promise<string> {
83
+ const ResponseContentDisposition = file.name
84
+ ? `attachment; filename=${encodeURIComponent(file.name)}`
85
+ : undefined;
86
+
87
+ const command = new GetObjectCommand({
88
+ Bucket: this.bucket,
89
+ Key: file.path,
90
+ ResponseContentDisposition,
91
+ });
92
+
93
+ return getSignedUrl(this.client, command, { expiresIn });
94
+ }
95
+
96
+ async getUploadURL(
97
+ params: GetUploadParams,
98
+ expiresIn = 60 * 60,
99
+ ): Promise<string> {
100
+ const command = new PutObjectCommand({
101
+ Bucket: this.bucket,
102
+ Key: params.path,
103
+ ContentType: params.contentType,
104
+ ContentLength: params.contentLength,
105
+ });
106
+
107
+ return getSignedUrl(this.client, command, { expiresIn });
108
+ }
109
+
110
+ async getUpload(
111
+ params: GetUploadParams,
112
+ expiresIn = 5,
113
+ ): Promise<GetUploadResponse> {
114
+ const { path } = params;
115
+ const { fields: values, url } = await createPresignedPost(this.client, {
116
+ Expires: expiresIn * 60,
117
+ Bucket: this.bucket,
118
+ Fields: {
119
+ acl: this.acl,
120
+ },
121
+ Conditions: [
122
+ // content length restrictions: 0-1MB]
123
+ // ['content-length-range', 0, contentLength],
124
+ // specify content-type to be more generic- images only
125
+ // ['starts-with', '$Content-Type', 'image/'],
126
+ // ['starts-with', '$Content-Type', contentType],
127
+ ],
128
+ Key: path,
129
+ });
130
+
131
+ const keys = Object.keys(values);
132
+ const fields = keys.map((key) => ({ key, value: values[key] || '' }));
133
+
134
+ return { url, fields };
135
+ }
136
+
137
+ async upload(
138
+ key: string,
139
+ data: string | Buffer,
140
+ contentType: string,
141
+ ): Promise<void> {
142
+ const Body = typeof data === 'string' ? Buffer.from(data, 'base64') : data;
143
+
144
+ const params = {
145
+ Bucket: this.bucket,
146
+ Key: key,
147
+ Body,
148
+ ContentType: contentType,
149
+ };
150
+
151
+ const command = new PutObjectCommand(params);
152
+
153
+ await this.client.send(command);
154
+ }
155
+ }
156
+
157
+ export enum AmazonCannedAccessControlList {
158
+ AuthenticatedRead = 'authenticated-read',
159
+ Private = 'private',
160
+ PublicRead = 'public-read',
161
+ PublicReadWrite = 'public-read-write',
162
+ AwsExecRead = 'aws-exec-read',
163
+ BucketOwnerRead = 'bucket-owner-read',
164
+ BucketOwnerFullControl = 'bucket-owner-full-control',
165
+ LogDeliveryWrite = 'log-delivery-write',
166
+ }
167
+
168
+ interface AmazonStorageClientCreateOptions {
169
+ bucket: string;
170
+ region?: string;
171
+ acl?: AmazonCannedAccessControlList;
172
+ accessKeyId?: string;
173
+ secretAccessKey?: string;
174
+ endpoint?: string;
175
+ forcePathStyle?: boolean;
176
+ }
@@ -0,0 +1,89 @@
1
+ export interface DocumentVersion {
2
+ id: string;
3
+ createdAt: Date;
4
+ }
5
+
6
+ export enum StorageProvider {
7
+ AWSS3 = 'geekimdas.toolbox.storage.aws.s3',
8
+ GCP = 'geekimdas.toolbox.storage.gcp',
9
+ AZURE = 'geekimdas.toolbox.storage.azure',
10
+ }
11
+
12
+ export interface StorageClient {
13
+ readonly provider: StorageProvider;
14
+ /**
15
+ * Get a URL to upload a file to.
16
+ *
17
+ * @param params - The parameters to get the upload URL for.
18
+ * @param expiresIn - The number of minutes the URL should be valid for.
19
+ * Defaults to 60 * 60 (1 hour).
20
+ * @returns A promise that resolves to the upload URL.
21
+ */
22
+ getUploadURL(params: GetUploadParams, expiresIn?: number): Promise<string>;
23
+
24
+ /**
25
+ * Get a URL to download a file from.
26
+ *
27
+ * @param file - The file to get the download URL for.
28
+ * @param expiresIn - The number of seconds the URL should be valid for.
29
+ * Defaults to 60 * 60 (1 hour).
30
+ * @returns A promise that resolves to the download URL.
31
+ */
32
+ getDownloadURL(file: File, expiresIn?: number): Promise<string>;
33
+ /**
34
+ * Get the versions for a key.
35
+ *
36
+ * @param key - The key to get the versions for.
37
+ */
38
+ getVersions(key: string): Promise<DocumentVersion[]>;
39
+
40
+ /**
41
+ * Get a URL to download a version of a file from.
42
+ *
43
+ * @param file - The file to get the download URL for.
44
+ * @param versionId - The version ID to get the download URL for.
45
+ */
46
+ getVersionDownloadURL(file: File, versionId: string): Promise<string>;
47
+ /**
48
+ *
49
+ * @param key - The key to upload the file to.
50
+ * @param data - The data to upload.
51
+ * @param contentType - The content type of the data.
52
+ */
53
+ upload(
54
+ key: string,
55
+ data: string | Buffer,
56
+ contentType: string,
57
+ ): Promise<void>;
58
+ /**
59
+ * Get a URL to upload a file to.
60
+ *
61
+ * @param params - The parameters to get the upload URL for.
62
+ * @param expiresIn - The number of minutes the URL should be valid for.
63
+ */
64
+ getUpload(
65
+ params: GetUploadParams,
66
+ expiresIn?: number,
67
+ ): Promise<GetUploadResponse>;
68
+ }
69
+
70
+ export interface GetUploadParams {
71
+ path: string;
72
+ contentType: string;
73
+ contentLength: number;
74
+ }
75
+
76
+ export type UploadField = {
77
+ key: string;
78
+ value: string;
79
+ };
80
+
81
+ export type GetUploadResponse = {
82
+ url: string;
83
+ fields: UploadField[];
84
+ };
85
+
86
+ export interface File {
87
+ name?: string;
88
+ path: string;
89
+ }
@@ -0,0 +1,327 @@
1
+ import { S3Client } from '@aws-sdk/client-s3';
2
+ import { beforeAll, describe, expect, it } from 'vitest';
3
+ import {
4
+ AmazonCannedAccessControlList,
5
+ AmazonStorageClient,
6
+ } from '../AmazonStorageClient';
7
+ import { StorageProvider } from '../StorageClient';
8
+
9
+ describe('AmazonStorageClient Integration Tests', () => {
10
+ let client: AmazonStorageClient;
11
+ const testBucket = 'geekmidas';
12
+ const testKey = 'test-files/test.txt';
13
+ const testContent = 'Hello, World!';
14
+ const testContentType = 'text/plain';
15
+
16
+ beforeAll(async () => {
17
+ // Create storage client pointing to MinIO
18
+ client = AmazonStorageClient.create({
19
+ bucket: testBucket,
20
+ region: 'us-east-1',
21
+ accessKeyId: 'geekmidas',
22
+ secretAccessKey: 'geekmidas',
23
+ endpoint: 'http://localhost:9000',
24
+ acl: AmazonCannedAccessControlList.PublicRead,
25
+ forcePathStyle: true,
26
+ });
27
+
28
+ // Wait for MinIO to be ready
29
+ await new Promise((resolve) => setTimeout(resolve, 2000));
30
+ });
31
+
32
+ describe('provider property', () => {
33
+ it('should return AWS S3 provider', () => {
34
+ expect(client.provider).toBe(StorageProvider.AWSS3);
35
+ });
36
+ });
37
+
38
+ describe('upload and download workflow', () => {
39
+ it.skip('should upload a file directly and generate download URL', async () => {
40
+ // Upload file directly
41
+ await client.upload(testKey, testContent, testContentType);
42
+
43
+ // Generate download URL
44
+ const downloadUrl = await client.getDownloadURL({ path: testKey });
45
+
46
+ expect(downloadUrl).toMatch(
47
+ /^http:\/\/localhost:9000\/geekmidas\/test-files\/test\.txt/,
48
+ );
49
+ expect(downloadUrl).toContain('X-Amz-Algorithm=AWS4-HMAC-SHA256');
50
+ expect(downloadUrl).toContain('X-Amz-Credential=');
51
+ expect(downloadUrl).toContain('X-Amz-Date=');
52
+ expect(downloadUrl).toContain('X-Amz-SignedHeaders=');
53
+ expect(downloadUrl).toContain('X-Amz-Signature=');
54
+
55
+ // Verify file can be downloaded
56
+ const response = await fetch(downloadUrl);
57
+ expect(response.ok).toBe(true);
58
+
59
+ const downloadedContent = await response.text();
60
+ expect(downloadedContent).toBe(testContent);
61
+ });
62
+
63
+ it('should upload a file with filename and generate download URL with content disposition', async () => {
64
+ const keyWithName = 'test-files/named-file.txt';
65
+ const fileName = 'my-document.txt';
66
+
67
+ await client.upload(keyWithName, testContent, testContentType);
68
+
69
+ const downloadUrl = await client.getDownloadURL({
70
+ path: keyWithName,
71
+ name: fileName,
72
+ });
73
+
74
+ expect(downloadUrl).toContain(
75
+ 'response-content-disposition=attachment%3B%20filename%3Dmy-document.txt',
76
+ );
77
+ });
78
+
79
+ it('should handle binary data upload', async () => {
80
+ const binaryKey = 'test-files/binary.bin';
81
+ const binaryData = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // "Hello" in bytes
82
+
83
+ await client.upload(binaryKey, binaryData, 'application/octet-stream');
84
+
85
+ const downloadUrl = await client.getDownloadURL({ path: binaryKey });
86
+ const response = await fetch(downloadUrl);
87
+ const downloadedBuffer = await response.arrayBuffer();
88
+
89
+ expect(new Uint8Array(downloadedBuffer)).toEqual(
90
+ new Uint8Array(binaryData),
91
+ );
92
+ });
93
+ });
94
+
95
+ describe('presigned upload URLs', () => {
96
+ it('should generate presigned PUT upload URL', async () => {
97
+ const uploadKey = 'test-files/presigned-upload.txt';
98
+ const uploadUrl = await client.getUploadURL({
99
+ path: uploadKey,
100
+ contentType: testContentType,
101
+ contentLength: testContent.length,
102
+ });
103
+
104
+ expect(uploadUrl).toMatch(
105
+ /^http:\/\/localhost:9000\/geekmidas\/test-files\/presigned-upload\.txt/,
106
+ );
107
+ expect(uploadUrl).toContain('X-Amz-Algorithm=AWS4-HMAC-SHA256');
108
+
109
+ // Test actual upload using the presigned URL
110
+ const uploadResponse = await fetch(uploadUrl, {
111
+ method: 'PUT',
112
+ headers: {
113
+ 'Content-Type': testContentType,
114
+ 'Content-Length': testContent.length.toString(),
115
+ },
116
+ body: testContent,
117
+ });
118
+
119
+ expect(uploadResponse.ok).toBe(true);
120
+
121
+ // Verify file was uploaded
122
+ const downloadUrl = await client.getDownloadURL({ path: uploadKey });
123
+ const downloadResponse = await fetch(downloadUrl);
124
+ const downloadedContent = await downloadResponse.text();
125
+ expect(downloadedContent).toBe(testContent);
126
+ });
127
+
128
+ it('should generate presigned POST upload with form fields', async () => {
129
+ const uploadKey = 'test-files/presigned-post.txt';
130
+ const uploadData = await client.getUpload({
131
+ path: uploadKey,
132
+ contentType: testContentType,
133
+ contentLength: testContent.length,
134
+ });
135
+
136
+ expect(uploadData.url).toBe('http://localhost:9000/geekmidas');
137
+ expect(uploadData.fields).toContainEqual({
138
+ key: 'key',
139
+ value: uploadKey,
140
+ });
141
+ expect(uploadData.fields).toContainEqual({
142
+ key: 'acl',
143
+ value: 'public-read',
144
+ });
145
+
146
+ // Test actual upload using the presigned POST
147
+ const formData = new FormData();
148
+ uploadData.fields.forEach(({ key, value }) => {
149
+ formData.append(key, value);
150
+ });
151
+ formData.append(
152
+ 'file',
153
+ new Blob([testContent], { type: testContentType }),
154
+ );
155
+
156
+ const uploadResponse = await fetch(uploadData.url, {
157
+ method: 'POST',
158
+ body: formData,
159
+ });
160
+
161
+ expect(uploadResponse.ok).toBe(true);
162
+
163
+ // Verify file was uploaded
164
+ const downloadUrl = await client.getDownloadURL({ path: uploadKey });
165
+ const downloadResponse = await fetch(downloadUrl);
166
+ const downloadedContent = await downloadResponse.blob();
167
+ expect(downloadedContent).toEqual(
168
+ new Blob([testContent], { type: testContentType }),
169
+ );
170
+ });
171
+ });
172
+
173
+ describe('versioning', () => {
174
+ it('should handle file versions', async () => {
175
+ const versionKey = 'test-files/versioned.txt';
176
+ const content1 = 'Version 1';
177
+ const content2 = 'Version 2';
178
+
179
+ // Upload first version
180
+ await client.upload(versionKey, content1, testContentType);
181
+
182
+ // Upload second version
183
+ await client.upload(versionKey, content2, testContentType);
184
+
185
+ // Get versions - MinIO might not enable versioning by default
186
+ // This test verifies the method works even if versioning is not enabled
187
+ const versions = await client.getVersions(versionKey);
188
+ expect(Array.isArray(versions)).toBe(true);
189
+
190
+ // If versioning is enabled, we should have multiple versions
191
+ // If not, we should have at least the current version
192
+ if (versions.length > 0) {
193
+ expect(versions[0]).toHaveProperty('id');
194
+ expect(versions[0]).toHaveProperty('createdAt');
195
+ expect(versions[0].createdAt).toBeInstanceOf(Date);
196
+ }
197
+ });
198
+ });
199
+
200
+ describe('error handling', () => {
201
+ it('should handle download URL generation for non-existent files', async () => {
202
+ // This should not throw - presigned URLs can be generated for non-existent files
203
+ const downloadUrl = await client.getDownloadURL({
204
+ path: 'non-existent/file.txt',
205
+ });
206
+ expect(downloadUrl).toMatch(
207
+ /^http:\/\/localhost:9000\/geekmidas\/non-existent\/file\.txt/,
208
+ );
209
+
210
+ // But accessing the URL should return 404
211
+ const response = await fetch(downloadUrl);
212
+ expect(response.status).toBe(404);
213
+ });
214
+ });
215
+
216
+ describe('custom expiration times', () => {
217
+ it('should respect custom expiration for download URLs', async () => {
218
+ const key = 'test-files/expiration-test.txt';
219
+ await client.upload(key, testContent, testContentType);
220
+
221
+ const shortExpirationUrl = await client.getDownloadURL({ path: key }, 1); // 1 second
222
+ const longExpirationUrl = await client.getDownloadURL(
223
+ { path: key },
224
+ 3600,
225
+ ); // 1 hour
226
+
227
+ expect(shortExpirationUrl).toContain('X-Amz-Expires=1');
228
+ expect(longExpirationUrl).toContain('X-Amz-Expires=3600');
229
+ });
230
+
231
+ it('should respect custom expiration for upload URLs', async () => {
232
+ const key = 'test-files/upload-expiration-test.txt';
233
+
234
+ const shortExpirationUrl = await client.getUploadURL(
235
+ {
236
+ path: key,
237
+ contentType: testContentType,
238
+ contentLength: testContent.length,
239
+ },
240
+ 1,
241
+ ); // 1 second
242
+
243
+ const longExpirationUrl = await client.getUploadURL(
244
+ {
245
+ path: key,
246
+ contentType: testContentType,
247
+ contentLength: testContent.length,
248
+ },
249
+ 3600,
250
+ ); // 1 hour
251
+
252
+ expect(shortExpirationUrl).toContain('X-Amz-Expires=1');
253
+ expect(longExpirationUrl).toContain('X-Amz-Expires=3600');
254
+ });
255
+ });
256
+
257
+ describe('factory method', () => {
258
+ it('should create client with minimal configuration', () => {
259
+ const minimalClient = AmazonStorageClient.create({
260
+ bucket: 'geekmidas',
261
+ });
262
+
263
+ expect(minimalClient).toBeInstanceOf(AmazonStorageClient);
264
+ expect(minimalClient.provider).toBe(StorageProvider.AWSS3);
265
+ });
266
+
267
+ it('should create client with full configuration', () => {
268
+ const fullClient = AmazonStorageClient.create({
269
+ bucket: 'geekmidas',
270
+ region: 'us-west-2',
271
+ accessKeyId: 'test-key',
272
+ secretAccessKey: 'test-secret',
273
+ endpoint: 'https://custom-s3.example.com',
274
+ acl: AmazonCannedAccessControlList.Private,
275
+ });
276
+
277
+ expect(fullClient).toBeInstanceOf(AmazonStorageClient);
278
+ expect(fullClient.provider).toBe(StorageProvider.AWSS3);
279
+ });
280
+ });
281
+
282
+ describe('different ACL configurations', () => {
283
+ it('should work with different ACL settings', async () => {
284
+ const privateClient = AmazonStorageClient.create({
285
+ bucket: testBucket,
286
+ region: 'us-east-1',
287
+ accessKeyId: 'minioadmin',
288
+ secretAccessKey: 'minioadmin',
289
+ endpoint: 'http://localhost:9000',
290
+ acl: AmazonCannedAccessControlList.Private,
291
+ });
292
+
293
+ const uploadData = await privateClient.getUpload({
294
+ path: 'test-files/private.txt',
295
+ contentType: testContentType,
296
+ contentLength: testContent.length,
297
+ });
298
+
299
+ expect(uploadData.fields).toContainEqual({
300
+ key: 'acl',
301
+ value: 'private',
302
+ });
303
+ });
304
+ });
305
+
306
+ describe('S3Client integration', () => {
307
+ it('should work with externally created S3Client', () => {
308
+ const s3Client = new S3Client({
309
+ region: 'us-east-1',
310
+ credentials: {
311
+ accessKeyId: 'minioadmin',
312
+ secretAccessKey: 'minioadmin',
313
+ },
314
+ endpoint: 'http://localhost:9000',
315
+ });
316
+
317
+ const storageClient = new AmazonStorageClient(
318
+ s3Client,
319
+ testBucket,
320
+ AmazonCannedAccessControlList.PublicRead,
321
+ );
322
+
323
+ expect(storageClient).toBeInstanceOf(AmazonStorageClient);
324
+ expect(storageClient.provider).toBe(StorageProvider.AWSS3);
325
+ });
326
+ });
327
+ });
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { StorageProvider } from '../StorageClient';
3
+
4
+ describe('StorageClient', () => {
5
+ describe('StorageProvider enum', () => {
6
+ it('should have correct provider values', () => {
7
+ expect(StorageProvider.AWSS3).toBe('geekimdas.toolbox.storage.aws.s3');
8
+ expect(StorageProvider.GCP).toBe('geekimdas.toolbox.storage.gcp');
9
+ expect(StorageProvider.AZURE).toBe('geekimdas.toolbox.storage.azure');
10
+ });
11
+
12
+ it('should have unique provider values', () => {
13
+ const values = Object.values(StorageProvider);
14
+ const uniqueValues = new Set(values);
15
+ expect(uniqueValues.size).toBe(values.length);
16
+ });
17
+ });
18
+
19
+ describe('interfaces', () => {
20
+ it('should define DocumentVersion interface correctly', () => {
21
+ // This is a compile-time test to ensure the interface exists
22
+ // and has the correct shape
23
+ const version: import('../StorageClient').DocumentVersion = {
24
+ id: 'test-id',
25
+ createdAt: new Date(),
26
+ };
27
+
28
+ expect(version.id).toBe('test-id');
29
+ expect(version.createdAt).toBeInstanceOf(Date);
30
+ });
31
+
32
+ it('should define File interface correctly', () => {
33
+ const file: import('../StorageClient').File = {
34
+ path: 'test/path.txt',
35
+ };
36
+
37
+ expect(file.path).toBe('test/path.txt');
38
+ expect(file.name).toBeUndefined();
39
+
40
+ const fileWithName: import('../StorageClient').File = {
41
+ path: 'test/path.txt',
42
+ name: 'display-name.txt',
43
+ };
44
+
45
+ expect(fileWithName.path).toBe('test/path.txt');
46
+ expect(fileWithName.name).toBe('display-name.txt');
47
+ });
48
+
49
+ it('should define GetUploadParams interface correctly', () => {
50
+ const params: import('../StorageClient').GetUploadParams = {
51
+ path: 'test/upload.txt',
52
+ contentType: 'text/plain',
53
+ contentLength: 100,
54
+ };
55
+
56
+ expect(params.path).toBe('test/upload.txt');
57
+ expect(params.contentType).toBe('text/plain');
58
+ expect(params.contentLength).toBe(100);
59
+ });
60
+
61
+ it('should define GetUploadResponse interface correctly', () => {
62
+ const response: import('../StorageClient').GetUploadResponse = {
63
+ url: 'https://example.com/upload',
64
+ fields: [
65
+ { key: 'key', value: 'test/path.txt' },
66
+ { key: 'acl', value: 'public-read' },
67
+ ],
68
+ };
69
+
70
+ expect(response.url).toBe('https://example.com/upload');
71
+ expect(response.fields).toHaveLength(2);
72
+ expect(response.fields[0]).toEqual({ key: 'key', value: 'test/path.txt' });
73
+ expect(response.fields[1]).toEqual({ key: 'acl', value: 'public-read' });
74
+ });
75
+
76
+ it('should define UploadField type correctly', () => {
77
+ const field: import('../StorageClient').UploadField = {
78
+ key: 'test-key',
79
+ value: 'test-value',
80
+ };
81
+
82
+ expect(field.key).toBe('test-key');
83
+ expect(field.value).toBe('test-value');
84
+ });
85
+ });
86
+ });