@travetto/web-upload 8.0.0-alpha.3 → 8.0.0-alpha.30
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/README.md +2 -3
- package/__index__.ts +4 -3
- package/package.json +18 -18
- package/src/config.ts +1 -1
- package/src/decorator.ts +21 -24
- package/src/interceptor.ts +9 -5
- package/src/trv.d.ts +3 -3
- package/src/types.ts +5 -3
- package/src/util.ts +32 -25
- package/support/test/server.ts +47 -34
package/README.md
CHANGED
|
@@ -15,18 +15,17 @@ yarn add @travetto/web-upload
|
|
|
15
15
|
|
|
16
16
|
This module provides a clean and direct mechanism for processing uploads, built upon [@fastify/busboy](https://github.com/fastify/busboy). The module also provides some best practices with respect to temporary file management.
|
|
17
17
|
|
|
18
|
-
Once the files are uploaded, they are exposed via [Endpoint](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L14) parameters using the [@Upload](https://github.com/travetto/travetto/tree/main/module/web-upload/src/decorator.ts#L20) decorator.
|
|
18
|
+
Once the files are uploaded, they are exposed via [Endpoint](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L14) parameters using the [@Upload](https://github.com/travetto/travetto/tree/main/module/web-upload/src/decorator.ts#L20) decorator. This decorator requires the related field type to be a standard [Buffer](https://nodejs.org/api/buffer.html#class-file) object, or a [FileMap](https://github.com/travetto/travetto/tree/main/module/web-upload/src/types.ts#L7).
|
|
19
19
|
|
|
20
20
|
A simple example:
|
|
21
21
|
|
|
22
22
|
**Code: Web controller with upload support**
|
|
23
23
|
```typescript
|
|
24
|
-
import { Controller,
|
|
24
|
+
import { Controller, Get, Post } from '@travetto/web';
|
|
25
25
|
import { type FileMap, Upload } from '@travetto/web-upload';
|
|
26
26
|
|
|
27
27
|
@Controller('/simple')
|
|
28
28
|
export class Simple {
|
|
29
|
-
|
|
30
29
|
@Get('/age')
|
|
31
30
|
getAge() {
|
|
32
31
|
return { age: 50 };
|
package/__index__.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {} from './src/trv.d.ts';
|
|
2
|
+
|
|
3
3
|
export * from './src/config.ts';
|
|
4
|
+
export * from './src/decorator.ts';
|
|
5
|
+
export * from './src/types.ts';
|
|
4
6
|
export * from './src/util.ts';
|
|
5
|
-
export * from './src/types.ts';
|
package/package.json
CHANGED
|
@@ -1,39 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/web-upload",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
4
|
-
"
|
|
3
|
+
"version": "8.0.0-alpha.30",
|
|
4
|
+
"private": false,
|
|
5
5
|
"description": "Provides integration between the travetto asset and web module.",
|
|
6
6
|
"keywords": [
|
|
7
|
-
"web",
|
|
8
7
|
"travetto",
|
|
9
8
|
"typescript",
|
|
10
|
-
"upload"
|
|
9
|
+
"upload",
|
|
10
|
+
"web"
|
|
11
11
|
],
|
|
12
12
|
"homepage": "https://travetto.io",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
15
|
+
"name": "Travetto Framework",
|
|
16
|
+
"email": "travetto.framework@gmail.com"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"url": "git+https://github.com/travetto/travetto.git",
|
|
20
|
+
"directory": "module/web-upload"
|
|
17
21
|
},
|
|
18
22
|
"files": [
|
|
19
23
|
"__index__.ts",
|
|
20
24
|
"src",
|
|
21
25
|
"support"
|
|
22
26
|
],
|
|
27
|
+
"type": "module",
|
|
23
28
|
"main": "__index__.ts",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"directory": "module/web-upload"
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
27
31
|
},
|
|
28
32
|
"dependencies": {
|
|
29
|
-
"@fastify/busboy": "^3.2.
|
|
30
|
-
"@travetto/config": "^8.0.0-alpha.
|
|
31
|
-
"@travetto/web": "^8.0.0-alpha.
|
|
32
|
-
"file-type": "^
|
|
33
|
+
"@fastify/busboy": "^3.2.2",
|
|
34
|
+
"@travetto/config": "^8.0.0-alpha.27",
|
|
35
|
+
"@travetto/web": "^8.0.0-alpha.30",
|
|
36
|
+
"file-type": "^22.0.2",
|
|
33
37
|
"mime": "^4.1.0"
|
|
34
38
|
},
|
|
35
39
|
"peerDependencies": {
|
|
36
|
-
"@travetto/test": "^8.0.0-alpha.
|
|
40
|
+
"@travetto/test": "^8.0.0-alpha.26"
|
|
37
41
|
},
|
|
38
42
|
"peerDependenciesMeta": {
|
|
39
43
|
"@travetto/test": {
|
|
@@ -42,9 +46,5 @@
|
|
|
42
46
|
},
|
|
43
47
|
"travetto": {
|
|
44
48
|
"displayName": "Web Upload Support"
|
|
45
|
-
},
|
|
46
|
-
"private": false,
|
|
47
|
-
"publishConfig": {
|
|
48
|
-
"access": "public"
|
|
49
49
|
}
|
|
50
50
|
}
|
package/src/config.ts
CHANGED
package/src/decorator.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ControllerRegistryIndex, type EndpointParameterConfig, Param } from '@travetto/web';
|
|
1
|
+
import { type ClassInstance, getClass, RuntimeError, toConcrete } from '@travetto/runtime';
|
|
3
2
|
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
3
|
+
import { ControllerRegistryIndex, type EndpointParameterConfig, Param } from '@travetto/web';
|
|
4
4
|
|
|
5
|
-
import { WebUploadInterceptor } from './interceptor.ts';
|
|
6
5
|
import type { WebUploadConfig } from './config.ts';
|
|
6
|
+
import { WebUploadInterceptor } from './interceptor.ts';
|
|
7
7
|
import type { FileMap } from './types.ts';
|
|
8
8
|
import { WebUploadUtil } from './util.ts';
|
|
9
9
|
|
|
@@ -18,9 +18,8 @@ const FileMapContract = toConcrete<FileMap>();
|
|
|
18
18
|
* @kind decorator
|
|
19
19
|
*/
|
|
20
20
|
export function Upload(
|
|
21
|
-
param: Partial<EndpointParameterConfig> & UploadConfig = {}
|
|
21
|
+
param: Partial<EndpointParameterConfig> & UploadConfig = {}
|
|
22
22
|
): (instance: ClassInstance, property: string, idx: number) => void {
|
|
23
|
-
|
|
24
23
|
const finalConfig = { ...param };
|
|
25
24
|
|
|
26
25
|
return (instance: ClassInstance, property: string, idx: number): void => {
|
|
@@ -30,28 +29,24 @@ export function Upload(
|
|
|
30
29
|
const getName = (): string => SchemaRegistryIndex.get(cls).getMethod(property).parameters[idx].name!;
|
|
31
30
|
|
|
32
31
|
adapter.registerFinalizeHandler(() => {
|
|
33
|
-
adapter.registerEndpointInterceptorConfig(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
maxSize: finalConfig.maxSize,
|
|
44
|
-
types: finalConfig.types,
|
|
45
|
-
cleanupFiles: finalConfig.cleanupFiles
|
|
46
|
-
}
|
|
32
|
+
adapter.registerEndpointInterceptorConfig(property, WebUploadInterceptor, {
|
|
33
|
+
applies: true,
|
|
34
|
+
maxSize: finalConfig.maxSize,
|
|
35
|
+
types: finalConfig.types,
|
|
36
|
+
cleanupFiles: finalConfig.cleanupFiles,
|
|
37
|
+
uploads: {
|
|
38
|
+
[getName()]: {
|
|
39
|
+
maxSize: finalConfig.maxSize,
|
|
40
|
+
types: finalConfig.types,
|
|
41
|
+
cleanupFiles: finalConfig.cleanupFiles
|
|
47
42
|
}
|
|
48
43
|
}
|
|
49
|
-
);
|
|
44
|
+
});
|
|
50
45
|
});
|
|
51
46
|
|
|
52
|
-
|
|
47
|
+
Param('body', {
|
|
53
48
|
...finalConfig,
|
|
54
|
-
extract:
|
|
49
|
+
extract: request => {
|
|
55
50
|
const input = SchemaRegistryIndex.get(cls).getMethod(property).parameters[idx];
|
|
56
51
|
|
|
57
52
|
if (!input) {
|
|
@@ -59,7 +54,9 @@ export function Upload(
|
|
|
59
54
|
}
|
|
60
55
|
|
|
61
56
|
if (!(input.type === Blob || input.type === File || input.type === FileMapContract)) {
|
|
62
|
-
throw new RuntimeError(
|
|
57
|
+
throw new RuntimeError(
|
|
58
|
+
`Cannot use upload decorator with ${input.type.name}, but only an ${Blob.name}, ${File.name} or ${FileMapContract.name}`
|
|
59
|
+
);
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
const isMap = input.type === FileMapContract;
|
|
@@ -68,4 +65,4 @@ export function Upload(
|
|
|
68
65
|
}
|
|
69
66
|
})(instance, property, idx);
|
|
70
67
|
};
|
|
71
|
-
}
|
|
68
|
+
}
|
package/src/interceptor.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Inject, Injectable } from '@travetto/di';
|
|
2
2
|
import {
|
|
3
|
-
BodyInterceptor,
|
|
4
|
-
|
|
3
|
+
BodyInterceptor,
|
|
4
|
+
DecompressInterceptor,
|
|
5
|
+
type WebChainedContext,
|
|
6
|
+
type WebInterceptor,
|
|
7
|
+
type WebInterceptorCategory,
|
|
8
|
+
type WebInterceptorContext,
|
|
9
|
+
type WebResponse
|
|
5
10
|
} from '@travetto/web';
|
|
6
11
|
|
|
7
12
|
import type { WebUploadConfig } from './config.ts';
|
|
@@ -9,7 +14,6 @@ import { WebUploadUtil } from './util.ts';
|
|
|
9
14
|
|
|
10
15
|
@Injectable()
|
|
11
16
|
export class WebUploadInterceptor implements WebInterceptor<WebUploadConfig> {
|
|
12
|
-
|
|
13
17
|
category: WebInterceptorCategory = 'request';
|
|
14
18
|
runsBefore = [BodyInterceptor];
|
|
15
19
|
dependsOn = [DecompressInterceptor];
|
|
@@ -24,7 +28,7 @@ export class WebUploadInterceptor implements WebInterceptor<WebUploadConfig> {
|
|
|
24
28
|
base.uploads ??= {};
|
|
25
29
|
// Override the uploads object with all the data from the inputs
|
|
26
30
|
for (const [key, config] of inputs.flatMap(inputConfig => Object.entries(inputConfig.uploads ?? {}))) {
|
|
27
|
-
Object.assign(base.uploads[key] ??= {}, config);
|
|
31
|
+
Object.assign((base.uploads[key] ??= {}), config);
|
|
28
32
|
}
|
|
29
33
|
return base;
|
|
30
34
|
}
|
|
@@ -50,4 +54,4 @@ export class WebUploadInterceptor implements WebInterceptor<WebUploadConfig> {
|
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
|
-
}
|
|
57
|
+
}
|
package/src/trv.d.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { SchemaTypeUtil } from '@travetto/schema';
|
|
|
4
4
|
/**
|
|
5
5
|
* @concrete
|
|
6
6
|
*/
|
|
7
|
-
export interface FileMap extends Record<string, File> {
|
|
7
|
+
export interface FileMap extends Record<string, File> {}
|
|
8
8
|
|
|
9
|
-
SchemaTypeUtil.register(
|
|
10
|
-
|
|
9
|
+
SchemaTypeUtil.register(
|
|
10
|
+
toConcrete<FileMap>(),
|
|
11
|
+
input => typeof input === 'object' && !!input && Object.values(input).every(v => v instanceof Blob)
|
|
12
|
+
);
|
package/src/util.ts
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
import { createReadStream, createWriteStream } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
2
|
import fs from 'node:fs/promises';
|
|
5
|
-
import
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
6
5
|
import { Transform } from 'node:stream';
|
|
6
|
+
import { pipeline } from 'node:stream/promises';
|
|
7
7
|
|
|
8
8
|
import busboy from '@fastify/busboy';
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import {
|
|
11
|
+
AsyncQueue,
|
|
12
|
+
BinaryMetadataUtil,
|
|
13
|
+
type BinaryStream,
|
|
14
|
+
type BinaryType,
|
|
15
|
+
BinaryUtil,
|
|
16
|
+
CodecUtil,
|
|
17
|
+
RuntimeError,
|
|
18
|
+
Util
|
|
19
|
+
} from '@travetto/runtime';
|
|
20
|
+
import { WebBodyUtil, WebCommonUtil, WebHeaderUtil, type WebRequest } from '@travetto/web';
|
|
12
21
|
|
|
13
22
|
import type { WebUploadConfig } from './config.ts';
|
|
14
23
|
import type { FileMap } from './types.ts';
|
|
15
24
|
|
|
16
25
|
const MULTIPART = new Set(['application/x-www-form-urlencoded', 'multipart/form-data']);
|
|
17
26
|
|
|
18
|
-
type UploadItem = { stream: BinaryStream
|
|
19
|
-
type FileType = { ext: string
|
|
27
|
+
type UploadItem = { stream: BinaryStream; filename?: string; field: string; contentType?: string };
|
|
28
|
+
type FileType = { ext: string; mime: string };
|
|
20
29
|
const WebUploadSymbol = Symbol();
|
|
21
30
|
|
|
22
31
|
/**
|
|
23
32
|
* Web upload utilities
|
|
24
33
|
*/
|
|
25
34
|
export class WebUploadUtil {
|
|
26
|
-
|
|
27
35
|
/**
|
|
28
36
|
* Write limiter
|
|
29
37
|
* @returns
|
|
@@ -39,14 +47,14 @@ export class WebUploadUtil {
|
|
|
39
47
|
} else {
|
|
40
48
|
callback(null, chunk);
|
|
41
49
|
}
|
|
42
|
-
}
|
|
50
|
+
}
|
|
43
51
|
});
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
/**
|
|
47
55
|
* Get all the uploads, separating multipart from direct
|
|
48
56
|
*/
|
|
49
|
-
static async *
|
|
57
|
+
static async *getUploads(request: WebRequest, config: Partial<WebUploadConfig>): AsyncIterable<UploadItem> {
|
|
50
58
|
if (!WebBodyUtil.isRawBinary(request.body)) {
|
|
51
59
|
throw new RuntimeError('No input stream provided for upload', { category: 'data' });
|
|
52
60
|
}
|
|
@@ -70,14 +78,14 @@ export class WebUploadUtil {
|
|
|
70
78
|
'content-length': request.headers.get('Content-Length')!,
|
|
71
79
|
'content-range': request.headers.get('Content-Range')!,
|
|
72
80
|
'content-encoding': request.headers.get('Content-Encoding')!,
|
|
73
|
-
'content-transfer-encoding': request.headers.get('Content-Transfer-Encoding')
|
|
81
|
+
'content-transfer-encoding': request.headers.get('Content-Transfer-Encoding')!
|
|
74
82
|
},
|
|
75
83
|
limits: { fileSize: largestMax }
|
|
76
84
|
})
|
|
77
85
|
.on('file', (field, stream, filename, _encoding, mimetype) => queue.add({ stream, filename, field, contentType: mimetype }))
|
|
78
86
|
.on('limit', field => queue.throw(new RuntimeError(`File size exceeded for ${field}`, { category: 'data' })))
|
|
79
87
|
.on('finish', () => queue.close())
|
|
80
|
-
.on('error',
|
|
88
|
+
.on('error', error => queue.throw(error instanceof Error ? error : new Error(`${error}`)));
|
|
81
89
|
|
|
82
90
|
// Upload
|
|
83
91
|
void BinaryUtil.pipeline(requestBody, uploadHandler).catch(err => queue.throw(err));
|
|
@@ -93,7 +101,7 @@ export class WebUploadUtil {
|
|
|
93
101
|
* Detect mime from request input, usually http headers
|
|
94
102
|
*/
|
|
95
103
|
static async detectMimeTypeFromRequestInput(location?: string, contentType?: string): Promise<FileType | undefined> {
|
|
96
|
-
const { Mime } =
|
|
104
|
+
const { Mime } = await import('mime');
|
|
97
105
|
const otherTypes = (await import('mime/types/other.js')).default;
|
|
98
106
|
const standardTypes = (await import('mime/types/standard.js')).default;
|
|
99
107
|
const checker = new Mime(standardTypes, otherTypes);
|
|
@@ -117,7 +125,7 @@ export class WebUploadUtil {
|
|
|
117
125
|
const { fromWebStream } = await import('strtok3');
|
|
118
126
|
const parser = new FileTypeParser();
|
|
119
127
|
const token = fromWebStream(BinaryUtil.toReadableStream(input));
|
|
120
|
-
cleanup = (): Promise<void> => token.close();
|
|
128
|
+
cleanup = (): Promise<void> => token.close().catch(() => {});
|
|
121
129
|
return await parser.fromTokenizer(token);
|
|
122
130
|
} finally {
|
|
123
131
|
await cleanup?.();
|
|
@@ -128,9 +136,10 @@ export class WebUploadUtil {
|
|
|
128
136
|
* Get file type
|
|
129
137
|
*/
|
|
130
138
|
static async getFileType(input: BinaryType, filename?: string, contentType?: string): Promise<FileType> {
|
|
131
|
-
return (
|
|
132
|
-
(await this.
|
|
133
|
-
{ ext: 'bin', mime: 'application/octet-stream' }
|
|
139
|
+
return (
|
|
140
|
+
(await this.detectMimeTypeFromBinary(input)) ??
|
|
141
|
+
(await this.detectMimeTypeFromRequestInput(filename, contentType)) ?? { ext: 'bin', mime: 'application/octet-stream' }
|
|
142
|
+
);
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
/**
|
|
@@ -143,16 +152,14 @@ export class WebUploadUtil {
|
|
|
143
152
|
filename = filename ? path.basename(filename) : `unknown_${Date.now()}`;
|
|
144
153
|
|
|
145
154
|
const location = path.resolve(uniqueDirectory, filename);
|
|
146
|
-
const remove = (): Promise<void> => fs.rm(location).catch(() => {
|
|
147
|
-
const mimeCheck = config.matcher ??= WebCommonUtil.mimeTypeMatcher(config.types);
|
|
155
|
+
const remove = (): Promise<void> => fs.rm(location).catch(() => {});
|
|
156
|
+
const mimeCheck = (config.matcher ??= WebCommonUtil.mimeTypeMatcher(config.types));
|
|
148
157
|
const response = (): BinaryStream => createReadStream(location);
|
|
149
158
|
|
|
150
159
|
try {
|
|
151
160
|
const target = createWriteStream(location);
|
|
152
161
|
|
|
153
|
-
await (config.maxSize ?
|
|
154
|
-
pipeline(stream, this.limitWrite(config.maxSize, field), target) :
|
|
155
|
-
pipeline(stream, target));
|
|
162
|
+
await (config.maxSize ? pipeline(stream, this.limitWrite(config.maxSize, field), target) : pipeline(stream, target));
|
|
156
163
|
|
|
157
164
|
const detected = await this.getFileType(response(), filename, contentType);
|
|
158
165
|
|
|
@@ -164,10 +171,10 @@ export class WebUploadUtil {
|
|
|
164
171
|
filename = `${filename}.${detected.ext}`;
|
|
165
172
|
}
|
|
166
173
|
|
|
167
|
-
const metadata = await BinaryMetadataUtil.compute(response, { contentType: detected.mime, filename
|
|
174
|
+
const metadata = await BinaryMetadataUtil.compute(response, { contentType: detected.mime, filename });
|
|
168
175
|
const file = BinaryMetadataUtil.defineBlob(new File([], ''), response, metadata);
|
|
169
176
|
Object.defineProperty(file, 'cleanup', {
|
|
170
|
-
value: () => config.cleanupFiles !== false && fs.rm(location).catch(() => {
|
|
177
|
+
value: () => config.cleanupFiles !== false && fs.rm(location).catch(() => {})
|
|
171
178
|
});
|
|
172
179
|
return file;
|
|
173
180
|
} catch (error) {
|
|
@@ -189,4 +196,4 @@ export class WebUploadUtil {
|
|
|
189
196
|
static setRequestUploads(request: WebRequest & { [WebUploadSymbol]?: FileMap }, uploads: FileMap): void {
|
|
190
197
|
request[WebUploadSymbol] ??= uploads;
|
|
191
198
|
}
|
|
192
|
-
}
|
|
199
|
+
}
|
package/support/test/server.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
+
import { Registry } from '@travetto/registry';
|
|
3
4
|
import { BinaryMetadataUtil, castTo } from '@travetto/runtime';
|
|
4
|
-
import { Controller, Post } from '@travetto/web';
|
|
5
5
|
import { BeforeAll, Suite, Test, TestFixtures } from '@travetto/test';
|
|
6
|
-
import {
|
|
6
|
+
import { Controller, Post } from '@travetto/web';
|
|
7
7
|
|
|
8
8
|
import { BaseWebSuite } from '@travetto/web/support/test/suite/base.ts';
|
|
9
9
|
|
|
@@ -14,7 +14,6 @@ const getHash = (blob: Blob) => BinaryMetadataUtil.read(blob)?.hash;
|
|
|
14
14
|
|
|
15
15
|
@Controller('/test/upload')
|
|
16
16
|
class TestUploadController {
|
|
17
|
-
|
|
18
17
|
@Post('/all')
|
|
19
18
|
async uploadAll(@Upload() uploads: FileMap): Promise<{ hash?: string } | undefined> {
|
|
20
19
|
for (const [, blob] of Object.entries(uploads)) {
|
|
@@ -45,18 +44,19 @@ class TestUploadController {
|
|
|
45
44
|
|
|
46
45
|
@Suite()
|
|
47
46
|
export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
48
|
-
|
|
49
47
|
fixture: TestFixtures;
|
|
50
48
|
|
|
51
|
-
async getUploads(...files: { name: string
|
|
49
|
+
async getUploads(...files: { name: string; resource: string; type?: string }[]): Promise<FormData> {
|
|
52
50
|
const data = new FormData();
|
|
53
|
-
await Promise.all(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
await Promise.all(
|
|
52
|
+
files.map(async ({ name, type, resource }) => {
|
|
53
|
+
const file = await this.fixture.readFile(resource);
|
|
54
|
+
if (type) {
|
|
55
|
+
Object.defineProperty(file, 'type', { get: () => type });
|
|
56
|
+
}
|
|
57
|
+
data.append(name, file);
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
60
|
return data;
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -72,7 +72,7 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
72
72
|
const response = await this.request<{ hash: string }>({ body: uploads, context: { httpMethod: 'POST', path: '/test/upload/all' } });
|
|
73
73
|
|
|
74
74
|
const file = await this.fixture.readBinaryStream('/logo.png');
|
|
75
|
-
assert(response.body?.hash === await BinaryMetadataUtil.hash(file, { hashAlgorithm: 'sha256' }));
|
|
75
|
+
assert(response.body?.hash === (await BinaryMetadataUtil.hash(file, { hashAlgorithm: 'sha256' })));
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
@Test()
|
|
@@ -82,7 +82,7 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
82
82
|
const response = await this.request<{ hash: string }>({ context: { httpMethod: 'POST', path: '/test/upload' }, body: sent });
|
|
83
83
|
|
|
84
84
|
const file = await this.fixture.readBinaryStream('/logo.png');
|
|
85
|
-
assert(response.body?.hash === await BinaryMetadataUtil.hash(file, { hashAlgorithm: 'sha256' }));
|
|
85
|
+
assert(response.body?.hash === (await BinaryMetadataUtil.hash(file, { hashAlgorithm: 'sha256' })));
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
@Test()
|
|
@@ -91,7 +91,7 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
91
91
|
const response = await this.request<{ hash: string }>({ body: uploads, context: { httpMethod: 'POST', path: '/test/upload' } });
|
|
92
92
|
|
|
93
93
|
const file = await this.fixture.readBinaryStream('/logo.png');
|
|
94
|
-
assert(response.body?.hash === await BinaryMetadataUtil.hash(file, { hashAlgorithm: 'sha256' }));
|
|
94
|
+
assert(response.body?.hash === (await BinaryMetadataUtil.hash(file, { hashAlgorithm: 'sha256' })));
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
@Test()
|
|
@@ -100,10 +100,11 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
100
100
|
{ name: 'file1', resource: 'logo.png', type: 'image/png' },
|
|
101
101
|
{ name: 'file2', resource: 'logo.png', type: 'image/png' }
|
|
102
102
|
);
|
|
103
|
-
const response = await this.request<{ hash1: string
|
|
103
|
+
const response = await this.request<{ hash1: string; hash2: string }>({
|
|
104
104
|
body: uploads,
|
|
105
105
|
context: {
|
|
106
|
-
httpMethod: 'POST',
|
|
106
|
+
httpMethod: 'POST',
|
|
107
|
+
path: '/test/upload/all-named'
|
|
107
108
|
}
|
|
108
109
|
});
|
|
109
110
|
const file = await this.fixture.readBinaryStream('/logo.png');
|
|
@@ -120,20 +121,26 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
120
121
|
{ name: 'file2', resource: 'logo.png', type: 'image/png' }
|
|
121
122
|
);
|
|
122
123
|
|
|
123
|
-
const badResponse = await this.request<{ hash1: string
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
const badResponse = await this.request<{ hash1: string; hash2: string }>(
|
|
125
|
+
{
|
|
126
|
+
body: uploadBad,
|
|
127
|
+
context: { httpMethod: 'POST', path: '/test/upload/all-named-custom' }
|
|
128
|
+
},
|
|
129
|
+
false
|
|
130
|
+
);
|
|
127
131
|
assert(badResponse.context.httpStatusCode === 400);
|
|
128
132
|
|
|
129
133
|
const uploads = await this.getUploads(
|
|
130
134
|
{ name: 'file1', resource: 'logo.gif', type: 'image/gif' },
|
|
131
135
|
{ name: 'file2', resource: 'logo.png', type: 'image/png' }
|
|
132
136
|
);
|
|
133
|
-
const response = await this.request<{ hash1: string
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
const response = await this.request<{ hash1: string; hash2: string }>(
|
|
138
|
+
{
|
|
139
|
+
body: uploads,
|
|
140
|
+
context: { httpMethod: 'POST', path: '/test/upload/all-named-custom' }
|
|
141
|
+
},
|
|
142
|
+
false
|
|
143
|
+
);
|
|
137
144
|
assert(response.context.httpStatusCode === 200);
|
|
138
145
|
|
|
139
146
|
const file1 = await this.fixture.readBinaryStream('/logo.gif');
|
|
@@ -153,20 +160,26 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
153
160
|
{ name: 'file2', resource: 'logo.png', type: 'image/png' }
|
|
154
161
|
);
|
|
155
162
|
|
|
156
|
-
const badResponse = await this.request<{ hash1: string
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
const badResponse = await this.request<{ hash1: string; hash2: string }>(
|
|
164
|
+
{
|
|
165
|
+
body: uploadBad,
|
|
166
|
+
context: { httpMethod: 'POST', path: '/test/upload/all-named-size' }
|
|
167
|
+
},
|
|
168
|
+
false
|
|
169
|
+
);
|
|
160
170
|
assert(badResponse.context.httpStatusCode === 400);
|
|
161
171
|
|
|
162
172
|
const uploads = await this.getUploads(
|
|
163
173
|
{ name: 'file1', resource: 'asset.yml', type: 'text/plain' },
|
|
164
174
|
{ name: 'file2', resource: 'logo.png', type: 'image/png' }
|
|
165
175
|
);
|
|
166
|
-
const response = await this.request<{ hash1: string
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
176
|
+
const response = await this.request<{ hash1: string; hash2: string }>(
|
|
177
|
+
{
|
|
178
|
+
body: uploads,
|
|
179
|
+
context: { httpMethod: 'POST', path: '/test/upload/all-named-size' }
|
|
180
|
+
},
|
|
181
|
+
false
|
|
182
|
+
);
|
|
170
183
|
assert(response.context.httpStatusCode === 200);
|
|
171
184
|
|
|
172
185
|
const file1 = await this.fixture.readBinaryStream('/asset.yml');
|
|
@@ -178,4 +191,4 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
178
191
|
assert(response.body?.hash1 === hash1);
|
|
179
192
|
assert(response.body?.hash2 === hash2);
|
|
180
193
|
}
|
|
181
|
-
}
|
|
194
|
+
}
|