@travetto/web-upload 7.1.4 → 8.0.0-alpha.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/README.md +1 -1
- package/package.json +4 -4
- package/src/decorator.ts +3 -3
- package/src/interceptor.ts +3 -4
- package/src/types.ts +7 -1
- package/src/util.ts +71 -75
- package/support/test/server.ts +23 -23
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ 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. 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#
|
|
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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/web-upload",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Provides integration between the travetto asset and web module.",
|
|
6
6
|
"keywords": [
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@fastify/busboy": "^3.2.0",
|
|
30
|
-
"@travetto/config": "^
|
|
31
|
-
"@travetto/web": "^
|
|
30
|
+
"@travetto/config": "^8.0.0-alpha.1",
|
|
31
|
+
"@travetto/web": "^8.0.0-alpha.1",
|
|
32
32
|
"file-type": "^21.3.0",
|
|
33
33
|
"mime": "^4.1.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@travetto/test": "^
|
|
36
|
+
"@travetto/test": "^8.0.0-alpha.1"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@travetto/test": {
|
package/src/decorator.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RuntimeError, toConcrete, type ClassInstance, getClass } from '@travetto/runtime';
|
|
2
2
|
import { ControllerRegistryIndex, type EndpointParameterConfig, Param } from '@travetto/web';
|
|
3
3
|
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
4
4
|
|
|
@@ -55,11 +55,11 @@ export function Upload(
|
|
|
55
55
|
const input = SchemaRegistryIndex.get(cls).getMethod(property).parameters[idx];
|
|
56
56
|
|
|
57
57
|
if (!input) {
|
|
58
|
-
throw new
|
|
58
|
+
throw new RuntimeError(`Unknown field type, ensure you are using ${Blob.name}, ${File.name} or ${FileMapContract.name}`);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
if (!(input.type === Blob || input.type === File || input.type === FileMapContract)) {
|
|
62
|
-
throw new
|
|
62
|
+
throw new RuntimeError(`Cannot use upload decorator with ${input.type.name}, but only an ${Blob.name}, ${File.name} or ${FileMapContract.name}`);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const isMap = input.type === FileMapContract;
|
package/src/interceptor.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
|
|
7
7
|
import type { WebUploadConfig } from './config.ts';
|
|
8
8
|
import { WebUploadUtil } from './util.ts';
|
|
9
|
-
import type { FileMap } from './types.ts';
|
|
10
9
|
|
|
11
10
|
@Injectable()
|
|
12
11
|
export class WebUploadInterceptor implements WebInterceptor<WebUploadConfig> {
|
|
@@ -35,7 +34,7 @@ export class WebUploadInterceptor implements WebInterceptor<WebUploadConfig> {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
async filter({ request, config, next }: WebChainedContext<WebUploadConfig>): Promise<WebResponse> {
|
|
38
|
-
const uploads:
|
|
37
|
+
const uploads: Record<string, File & { cleanup?: () => Promise<void> }> = {};
|
|
39
38
|
|
|
40
39
|
try {
|
|
41
40
|
for await (const item of WebUploadUtil.getUploads(request, config)) {
|
|
@@ -46,8 +45,8 @@ export class WebUploadInterceptor implements WebInterceptor<WebUploadConfig> {
|
|
|
46
45
|
|
|
47
46
|
return await next();
|
|
48
47
|
} finally {
|
|
49
|
-
for (const
|
|
50
|
-
await
|
|
48
|
+
for (const item of Object.values(uploads)) {
|
|
49
|
+
await item.cleanup?.();
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
52
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { toConcrete } from '@travetto/runtime';
|
|
2
|
+
import { SchemaTypeUtil } from '@travetto/schema';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* @concrete
|
|
3
6
|
*/
|
|
4
|
-
export interface FileMap extends Record<string, File> { }
|
|
7
|
+
export interface FileMap extends Record<string, File> { }
|
|
8
|
+
|
|
9
|
+
SchemaTypeUtil.register(toConcrete<FileMap>(),
|
|
10
|
+
input => typeof input === 'object' && !!input && Object.values(input).every(v => v instanceof Blob));
|
package/src/util.ts
CHANGED
|
@@ -3,21 +3,20 @@ import path from 'node:path';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import fs from 'node:fs/promises';
|
|
5
5
|
import { pipeline } from 'node:stream/promises';
|
|
6
|
-
import {
|
|
6
|
+
import { Transform } from 'node:stream';
|
|
7
7
|
|
|
8
8
|
import busboy from '@fastify/busboy';
|
|
9
9
|
|
|
10
10
|
import { type WebRequest, WebCommonUtil, WebBodyUtil, WebHeaderUtil } from '@travetto/web';
|
|
11
|
-
import { AsyncQueue,
|
|
11
|
+
import { AsyncQueue, RuntimeError, CodecUtil, Util, BinaryUtil, type BinaryType, type BinaryStream, BinaryMetadataUtil } from '@travetto/runtime';
|
|
12
12
|
|
|
13
13
|
import type { WebUploadConfig } from './config.ts';
|
|
14
14
|
import type { FileMap } from './types.ts';
|
|
15
15
|
|
|
16
16
|
const MULTIPART = new Set(['application/x-www-form-urlencoded', 'multipart/form-data']);
|
|
17
17
|
|
|
18
|
-
type UploadItem = { stream:
|
|
18
|
+
type UploadItem = { stream: BinaryStream, filename?: string, field: string, contentType?: string };
|
|
19
19
|
type FileType = { ext: string, mime: string };
|
|
20
|
-
const RawFileSymbol = Symbol();
|
|
21
20
|
const WebUploadSymbol = Symbol();
|
|
22
21
|
|
|
23
22
|
/**
|
|
@@ -32,10 +31,11 @@ export class WebUploadUtil {
|
|
|
32
31
|
static limitWrite(maxSize: number, field?: string): Transform {
|
|
33
32
|
let read = 0;
|
|
34
33
|
return new Transform({
|
|
35
|
-
transform(
|
|
36
|
-
|
|
34
|
+
transform(input, encoding, callback): void {
|
|
35
|
+
const chunk = CodecUtil.readChunk(input, encoding);
|
|
36
|
+
read += chunk.byteLength;
|
|
37
37
|
if (read > maxSize) {
|
|
38
|
-
callback(new
|
|
38
|
+
callback(new RuntimeError('File size exceeded', { category: 'data', details: { read, size: maxSize, field } }));
|
|
39
39
|
} else {
|
|
40
40
|
callback(null, chunk);
|
|
41
41
|
}
|
|
@@ -43,22 +43,15 @@ export class WebUploadUtil {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* Get uploaded file path location
|
|
48
|
-
*/
|
|
49
|
-
static getUploadLocation(file: File): string {
|
|
50
|
-
return castTo<{ [RawFileSymbol]: string }>(file)[RawFileSymbol];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
46
|
/**
|
|
54
47
|
* Get all the uploads, separating multipart from direct
|
|
55
48
|
*/
|
|
56
|
-
static async* getUploads(request: WebRequest, config: Partial<WebUploadConfig>): AsyncIterable<UploadItem> {
|
|
57
|
-
if (!WebBodyUtil.
|
|
58
|
-
throw new
|
|
49
|
+
static async * getUploads(request: WebRequest, config: Partial<WebUploadConfig>): AsyncIterable<UploadItem> {
|
|
50
|
+
if (!WebBodyUtil.isRawBinary(request.body)) {
|
|
51
|
+
throw new RuntimeError('No input stream provided for upload', { category: 'data' });
|
|
59
52
|
}
|
|
60
53
|
|
|
61
|
-
const
|
|
54
|
+
const requestBody = request.body;
|
|
62
55
|
request.body = undefined;
|
|
63
56
|
|
|
64
57
|
const contentType = WebHeaderUtil.parseHeaderSegment(request.headers.get('Content-Type'));
|
|
@@ -70,8 +63,7 @@ export class WebUploadUtil {
|
|
|
70
63
|
const largestMax = fileMaxes.length ? Math.max(...fileMaxes) : config.maxSize;
|
|
71
64
|
const queue = new AsyncQueue<UploadItem>();
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
bodyStream.pipe(busboy({
|
|
66
|
+
const uploadHandler = busboy({
|
|
75
67
|
headers: {
|
|
76
68
|
'content-type': request.headers.get('Content-Type')!,
|
|
77
69
|
'content-disposition': request.headers.get('Content-Disposition')!,
|
|
@@ -82,22 +74,69 @@ export class WebUploadUtil {
|
|
|
82
74
|
},
|
|
83
75
|
limits: { fileSize: largestMax }
|
|
84
76
|
})
|
|
85
|
-
.on('file', (field, stream, filename) => queue.add({ stream, filename, field }))
|
|
86
|
-
.on('limit', field => queue.throw(new
|
|
77
|
+
.on('file', (field, stream, filename, _encoding, mimetype) => queue.add({ stream, filename, field, contentType: mimetype }))
|
|
78
|
+
.on('limit', field => queue.throw(new RuntimeError(`File size exceeded for ${field}`, { category: 'data' })))
|
|
87
79
|
.on('finish', () => queue.close())
|
|
88
|
-
.on('error', (error) => queue.throw(error instanceof Error ? error : new Error(`${error}`)))
|
|
80
|
+
.on('error', (error) => queue.throw(error instanceof Error ? error : new Error(`${error}`)));
|
|
81
|
+
|
|
82
|
+
// Upload
|
|
83
|
+
void BinaryUtil.pipeline(requestBody, uploadHandler).catch(err => queue.throw(err));
|
|
89
84
|
|
|
90
85
|
yield* queue;
|
|
91
86
|
} else {
|
|
92
87
|
const filename = WebHeaderUtil.parseHeaderSegment(request.headers.get('Content-Disposition')).parameters.filename;
|
|
93
|
-
yield { stream:
|
|
88
|
+
yield { stream: BinaryUtil.toBinaryStream(requestBody), filename, field: 'file', contentType: contentType.value };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Detect mime from request input, usually http headers
|
|
94
|
+
*/
|
|
95
|
+
static async detectMimeTypeFromRequestInput(location?: string, contentType?: string): Promise<FileType | undefined> {
|
|
96
|
+
const { Mime } = (await import('mime'));
|
|
97
|
+
const otherTypes = (await import('mime/types/other.js')).default;
|
|
98
|
+
const standardTypes = (await import('mime/types/standard.js')).default;
|
|
99
|
+
const checker = new Mime(standardTypes, otherTypes);
|
|
100
|
+
if (contentType) {
|
|
101
|
+
return { ext: checker.getExtension(contentType)!, mime: contentType };
|
|
102
|
+
} else if (location) {
|
|
103
|
+
const mime = checker.getType(location);
|
|
104
|
+
if (mime) {
|
|
105
|
+
return { mime, ext: checker.getExtension(mime)! };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Detect mime from the binary source
|
|
112
|
+
*/
|
|
113
|
+
static async detectMimeTypeFromBinary(input: BinaryType): Promise<FileType | undefined> {
|
|
114
|
+
let cleanup: (() => Promise<void>) | undefined;
|
|
115
|
+
try {
|
|
116
|
+
const { FileTypeParser } = await import('file-type');
|
|
117
|
+
const { fromWebStream } = await import('strtok3');
|
|
118
|
+
const parser = new FileTypeParser();
|
|
119
|
+
const token = fromWebStream(BinaryUtil.toReadableStream(input));
|
|
120
|
+
cleanup = (): Promise<void> => token.close();
|
|
121
|
+
return await parser.fromTokenizer(token);
|
|
122
|
+
} finally {
|
|
123
|
+
await cleanup?.();
|
|
94
124
|
}
|
|
95
125
|
}
|
|
96
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Get file type
|
|
129
|
+
*/
|
|
130
|
+
static async getFileType(input: BinaryType, filename?: string, contentType?: string): Promise<FileType> {
|
|
131
|
+
return (await this.detectMimeTypeFromBinary(input)) ??
|
|
132
|
+
(await this.detectMimeTypeFromRequestInput(filename, contentType)) ??
|
|
133
|
+
{ ext: 'bin', mime: 'application/octet-stream' };
|
|
134
|
+
}
|
|
135
|
+
|
|
97
136
|
/**
|
|
98
137
|
* Convert an UploadItem to a File
|
|
99
138
|
*/
|
|
100
|
-
static async toFile({ stream, filename, field }: UploadItem, config: Partial<WebUploadConfig>): Promise<File> {
|
|
139
|
+
static async toFile({ stream, filename, field, contentType }: UploadItem, config: Partial<WebUploadConfig>): Promise<File> {
|
|
101
140
|
const uniqueDirectory = path.resolve(os.tmpdir(), `file_${Date.now()}_${Util.uuid(5)}`);
|
|
102
141
|
await fs.mkdir(uniqueDirectory, { recursive: true });
|
|
103
142
|
|
|
@@ -106,6 +145,7 @@ export class WebUploadUtil {
|
|
|
106
145
|
const location = path.resolve(uniqueDirectory, filename);
|
|
107
146
|
const remove = (): Promise<void> => fs.rm(location).catch(() => { });
|
|
108
147
|
const mimeCheck = config.matcher ??= WebCommonUtil.mimeTypeMatcher(config.types);
|
|
148
|
+
const response = (): BinaryStream => createReadStream(location);
|
|
109
149
|
|
|
110
150
|
try {
|
|
111
151
|
const target = createWriteStream(location);
|
|
@@ -114,25 +154,21 @@ export class WebUploadUtil {
|
|
|
114
154
|
pipeline(stream, this.limitWrite(config.maxSize, field), target) :
|
|
115
155
|
pipeline(stream, target));
|
|
116
156
|
|
|
117
|
-
const detected = await this.getFileType(
|
|
157
|
+
const detected = await this.getFileType(response(), filename, contentType);
|
|
118
158
|
|
|
119
159
|
if (!mimeCheck(detected.mime)) {
|
|
120
|
-
throw new
|
|
160
|
+
throw new RuntimeError(`Content type not allowed: ${detected.mime}`, { category: 'data' });
|
|
121
161
|
}
|
|
122
162
|
|
|
123
163
|
if (!path.extname(filename)) {
|
|
124
164
|
filename = `${filename}.${detected.ext}`;
|
|
125
165
|
}
|
|
126
166
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
size: (await fs.stat(location)).size,
|
|
167
|
+
const metadata = await BinaryMetadataUtil.compute(response, { contentType: detected.mime, filename, });
|
|
168
|
+
const file = BinaryMetadataUtil.defineBlob(new File([], ''), response, metadata);
|
|
169
|
+
Object.defineProperty(file, 'cleanup', {
|
|
170
|
+
value: () => config.cleanupFiles !== false && fs.rm(location).catch(() => { })
|
|
132
171
|
});
|
|
133
|
-
|
|
134
|
-
Object.assign(file, { [RawFileSymbol]: location });
|
|
135
|
-
|
|
136
172
|
return file;
|
|
137
173
|
} catch (error) {
|
|
138
174
|
await remove();
|
|
@@ -140,46 +176,6 @@ export class WebUploadUtil {
|
|
|
140
176
|
}
|
|
141
177
|
}
|
|
142
178
|
|
|
143
|
-
/**
|
|
144
|
-
* Get file type
|
|
145
|
-
*/
|
|
146
|
-
static async getFileType(input: string | Readable): Promise<FileType> {
|
|
147
|
-
const { FileTypeParser } = await import('file-type');
|
|
148
|
-
const { fromStream } = await import('strtok3');
|
|
149
|
-
|
|
150
|
-
const parser = new FileTypeParser();
|
|
151
|
-
let token: ReturnType<typeof fromStream> | undefined;
|
|
152
|
-
let matched: FileType | undefined;
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
token = await fromStream(typeof input === 'string' ? createReadStream(input) : input);
|
|
156
|
-
matched = await parser.fromTokenizer(token);
|
|
157
|
-
} finally {
|
|
158
|
-
await token?.close();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (!matched && typeof input === 'string') {
|
|
162
|
-
const { Mime } = (await import('mime'));
|
|
163
|
-
const otherTypes = (await import('mime/types/other.js')).default;
|
|
164
|
-
const standardTypes = (await import('mime/types/standard.js')).default;
|
|
165
|
-
const checker = new Mime(standardTypes, otherTypes);
|
|
166
|
-
const mime = checker.getType(input);
|
|
167
|
-
if (mime) {
|
|
168
|
-
return { ext: checker.getExtension(mime)!, mime };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return matched ?? { ext: 'bin', mime: 'application/octet-stream' };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Finish upload
|
|
176
|
-
*/
|
|
177
|
-
static async finishUpload(upload: File, config: Partial<WebUploadConfig>): Promise<void> {
|
|
178
|
-
if (config.cleanupFiles !== false) {
|
|
179
|
-
await fs.rm(this.getUploadLocation(upload), { force: true });
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
179
|
/**
|
|
184
180
|
* Get Uploads
|
|
185
181
|
*/
|
package/support/test/server.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { BinaryMetadataUtil, castTo } from '@travetto/runtime';
|
|
4
4
|
import { Controller, Post } from '@travetto/web';
|
|
5
5
|
import { BeforeAll, Suite, Test, TestFixtures } from '@travetto/test';
|
|
6
6
|
import { Registry } from '@travetto/registry';
|
|
@@ -10,7 +10,7 @@ import { BaseWebSuite } from '@travetto/web/support/test/suite/base.ts';
|
|
|
10
10
|
import { Upload } from '../../src/decorator.ts';
|
|
11
11
|
import type { FileMap } from '../../src/types.ts';
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const getHash = (blob: Blob) => BinaryMetadataUtil.read(blob)?.hash;
|
|
14
14
|
|
|
15
15
|
@Controller('/test/upload')
|
|
16
16
|
class TestUploadController {
|
|
@@ -18,28 +18,28 @@ class TestUploadController {
|
|
|
18
18
|
@Post('/all')
|
|
19
19
|
async uploadAll(@Upload() uploads: FileMap): Promise<{ hash?: string } | undefined> {
|
|
20
20
|
for (const [, blob] of Object.entries(uploads)) {
|
|
21
|
-
return { hash:
|
|
21
|
+
return { hash: getHash(blob) };
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
@Post('/')
|
|
26
26
|
async upload(@Upload() file: File) {
|
|
27
|
-
return { hash:
|
|
27
|
+
return { hash: getHash(file) };
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
@Post('/all-named')
|
|
31
31
|
async uploads(@Upload() file1: Blob, @Upload() file2: Blob) {
|
|
32
|
-
return { hash1:
|
|
32
|
+
return { hash1: getHash(file1), hash2: getHash(file2) };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
@Post('/all-named-custom')
|
|
36
36
|
async uploadVariousLimits(@Upload({ types: ['!image/png'] }) file1: Blob, @Upload() file2: Blob) {
|
|
37
|
-
return { hash1:
|
|
37
|
+
return { hash1: getHash(file1), hash2: getHash(file2) };
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
@Post('/all-named-size')
|
|
41
41
|
async uploadVariousSizeLimits(@Upload({ maxSize: 100 }) file1: File, @Upload({ maxSize: 8000 }) file2: File) {
|
|
42
|
-
return { hash1:
|
|
42
|
+
return { hash1: getHash(file1), hash2: getHash(file2) };
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -71,8 +71,8 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
71
71
|
const uploads = await this.getUploads({ name: 'random', resource: 'logo.png', type: 'image/png' });
|
|
72
72
|
const response = await this.request<{ hash: string }>({ body: uploads, context: { httpMethod: 'POST', path: '/test/upload/all' } });
|
|
73
73
|
|
|
74
|
-
const file = await this.fixture.
|
|
75
|
-
assert(response.body?.hash === await
|
|
74
|
+
const file = await this.fixture.readBinaryStream('/logo.png');
|
|
75
|
+
assert(response.body?.hash === await BinaryMetadataUtil.hash(file, { hashAlgorithm: 'sha256' }));
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
@Test()
|
|
@@ -81,8 +81,8 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
81
81
|
const sent = castTo<Blob>(uploads.get('file'));
|
|
82
82
|
const response = await this.request<{ hash: string }>({ context: { httpMethod: 'POST', path: '/test/upload' }, body: sent });
|
|
83
83
|
|
|
84
|
-
const file = await this.fixture.
|
|
85
|
-
assert(response.body?.hash === await
|
|
84
|
+
const file = await this.fixture.readBinaryStream('/logo.png');
|
|
85
|
+
assert(response.body?.hash === await BinaryMetadataUtil.hash(file, { hashAlgorithm: 'sha256' }));
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
@Test()
|
|
@@ -90,8 +90,8 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
90
90
|
const uploads = await this.getUploads({ name: 'file', resource: 'logo.png', type: 'image/png' });
|
|
91
91
|
const response = await this.request<{ hash: string }>({ body: uploads, context: { httpMethod: 'POST', path: '/test/upload' } });
|
|
92
92
|
|
|
93
|
-
const file = await this.fixture.
|
|
94
|
-
assert(response.body?.hash === await
|
|
93
|
+
const file = await this.fixture.readBinaryStream('/logo.png');
|
|
94
|
+
assert(response.body?.hash === await BinaryMetadataUtil.hash(file, { hashAlgorithm: 'sha256' }));
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
@Test()
|
|
@@ -106,8 +106,8 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
106
106
|
httpMethod: 'POST', path: '/test/upload/all-named'
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
|
-
const file = await this.fixture.
|
|
110
|
-
const hash = await
|
|
109
|
+
const file = await this.fixture.readBinaryStream('/logo.png');
|
|
110
|
+
const hash = await BinaryMetadataUtil.hash(file, { hashAlgorithm: 'sha256' });
|
|
111
111
|
|
|
112
112
|
assert(response.body?.hash1 === hash);
|
|
113
113
|
assert(response.body?.hash2 === hash);
|
|
@@ -136,11 +136,11 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
136
136
|
}, false);
|
|
137
137
|
assert(response.context.httpStatusCode === 200);
|
|
138
138
|
|
|
139
|
-
const file1 = await this.fixture.
|
|
140
|
-
const hash1 = await
|
|
139
|
+
const file1 = await this.fixture.readBinaryStream('/logo.gif');
|
|
140
|
+
const hash1 = await BinaryMetadataUtil.hash(file1, { hashAlgorithm: 'sha256' });
|
|
141
141
|
|
|
142
|
-
const file2 = await this.fixture.
|
|
143
|
-
const hash2 = await
|
|
142
|
+
const file2 = await this.fixture.readBinaryStream('/logo.png');
|
|
143
|
+
const hash2 = await BinaryMetadataUtil.hash(file2, { hashAlgorithm: 'sha256' });
|
|
144
144
|
|
|
145
145
|
assert(response.body?.hash1 === hash1);
|
|
146
146
|
assert(response.body?.hash2 === hash2);
|
|
@@ -169,11 +169,11 @@ export abstract class WebUploadServerSuite extends BaseWebSuite {
|
|
|
169
169
|
}, false);
|
|
170
170
|
assert(response.context.httpStatusCode === 200);
|
|
171
171
|
|
|
172
|
-
const file1 = await this.fixture.
|
|
173
|
-
const hash1 = await
|
|
172
|
+
const file1 = await this.fixture.readBinaryStream('/asset.yml');
|
|
173
|
+
const hash1 = await BinaryMetadataUtil.hash(file1, { hashAlgorithm: 'sha256' });
|
|
174
174
|
|
|
175
|
-
const file2 = await this.fixture.
|
|
176
|
-
const hash2 = await
|
|
175
|
+
const file2 = await this.fixture.readBinaryStream('/logo.png');
|
|
176
|
+
const hash2 = await BinaryMetadataUtil.hash(file2, { hashAlgorithm: 'sha256' });
|
|
177
177
|
|
|
178
178
|
assert(response.body?.hash1 === hash1);
|
|
179
179
|
assert(response.body?.hash2 === hash2);
|