@blamejs/blamejs-shop 0.5.7 → 0.5.9
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/CHANGELOG.md +4 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/cost-layers.js +86 -49
- package/lib/customers.js +33 -3
- package/lib/plan-changes.js +14 -0
- package/lib/vendor/MANIFEST.json +31 -15
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/api-snapshot.json +2 -2
- package/lib/vendor/blamejs/examples/wiki/package-lock.json +1 -1
- package/lib/vendor/blamejs/lib/content-credentials.js +43 -2
- package/lib/vendor/blamejs/lib/guard-sql.js +29 -0
- package/lib/vendor/blamejs/lib/mcp.js +37 -4
- package/lib/vendor/blamejs/lib/metrics.js +15 -3
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +9 -2
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +33 -2
- package/lib/vendor/blamejs/lib/safe-buffer.js +11 -1
- package/lib/vendor/blamejs/package-lock.json +2 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.16.3.json +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js +319 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cycle2-bugfixes.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-sql-coverage.test.js +432 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js +443 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js +395 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-tus-upload-coverage.test.js +402 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-yaml-coverage.test.js +292 -0
- package/package.json +1 -1
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* b.middleware.tusUpload — ERROR + ADVERSARIAL branch coverage.
|
|
6
|
+
*
|
|
7
|
+
* The happy path (POST create → PATCH append → HEAD → DELETE) is already
|
|
8
|
+
* exercised elsewhere. This file drives the UNCOVERED refusal / limit /
|
|
9
|
+
* malformed-input branches through the public middleware, plus the
|
|
10
|
+
* memoryStore contract's rejection paths:
|
|
11
|
+
*
|
|
12
|
+
* - config-time throws (memoryStore + tusUpload opts validation)
|
|
13
|
+
* - the Tus-Resumable version gate (§2.2) and path routing
|
|
14
|
+
* - POST creation refusals (bad / oversized / missing Upload-Length,
|
|
15
|
+
* malformed Upload-Metadata, store.create failure → 500)
|
|
16
|
+
* - PATCH refusals (wrong Content-Type, bad Upload-Offset, offset
|
|
17
|
+
* mismatch, checksum ext disabled / malformed / unsupported /
|
|
18
|
+
* mismatch → 460, oversized chunk, deferred-length finalization)
|
|
19
|
+
* - HEAD / DELETE not-found + extension-disabled refusals
|
|
20
|
+
* - the 405 method-not-allowed fall-through Allow header
|
|
21
|
+
*
|
|
22
|
+
* Every assertion drives the real consumer path with in-memory request /
|
|
23
|
+
* response fakes; nothing here needs a live network backend. A handful of
|
|
24
|
+
* branches surfaced framework bugs — those are asserted only for the
|
|
25
|
+
* fail-closed invariant that DOES hold (request refused, no bytes written)
|
|
26
|
+
* and reported separately; no assertion locks in the buggy status code.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
var EventEmitter = require("node:events").EventEmitter;
|
|
30
|
+
var helpers = require("../helpers");
|
|
31
|
+
var check = helpers.check;
|
|
32
|
+
var b = helpers.b;
|
|
33
|
+
var C = b.constants;
|
|
34
|
+
|
|
35
|
+
var tusUpload = b.middleware.tusUpload;
|
|
36
|
+
var VER = "1.0.0";
|
|
37
|
+
var OCTET = "application/offset+octet-stream";
|
|
38
|
+
|
|
39
|
+
// A streaming request the PATCH / creation-with-upload handlers can pump
|
|
40
|
+
// via req.on("data"|"end"); b.testing.bodyReq hardcodes url:"/" so it
|
|
41
|
+
// can't address a resource path, hence this small local builder.
|
|
42
|
+
function _sreq(method, url, headers, body) {
|
|
43
|
+
var req = new EventEmitter();
|
|
44
|
+
req.method = method;
|
|
45
|
+
req.url = url;
|
|
46
|
+
req.headers = headers || {};
|
|
47
|
+
req.socket = { remoteAddress: "127.0.0.1" };
|
|
48
|
+
req.destroy = function () { /* mock — no-op */ };
|
|
49
|
+
setImmediate(function () {
|
|
50
|
+
if (body !== undefined && body !== null) {
|
|
51
|
+
req.emit("data", Buffer.isBuffer(body) ? body : Buffer.from(body));
|
|
52
|
+
}
|
|
53
|
+
req.emit("end");
|
|
54
|
+
});
|
|
55
|
+
return req;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Plain (bodyless) request for HEAD / DELETE / OPTIONS / bodyless POST /
|
|
59
|
+
// method-not-allowed probes — those handlers never touch the stream.
|
|
60
|
+
function _req(method, url, headers) {
|
|
61
|
+
return b.testing.mockReq({ method: method, url: url, headers: headers || {} });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Run the middleware once; return the captured response plus whether it
|
|
65
|
+
// delegated via next().
|
|
66
|
+
async function _drive(tus, req) {
|
|
67
|
+
var res = b.testing.mockRes();
|
|
68
|
+
var nextCalled = false;
|
|
69
|
+
await tus(req, res, function () { nextCalled = true; });
|
|
70
|
+
var cap = res._captured();
|
|
71
|
+
cap.nextCalled = nextCalled;
|
|
72
|
+
return cap;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function _mkTus(overrides) {
|
|
76
|
+
var base = {
|
|
77
|
+
mountPath: "/uploads",
|
|
78
|
+
store: tusUpload.memoryStore({}),
|
|
79
|
+
audit: false,
|
|
80
|
+
};
|
|
81
|
+
if (overrides) {
|
|
82
|
+
var keys = Object.keys(overrides);
|
|
83
|
+
for (var i = 0; i < keys.length; i++) base[keys[i]] = overrides[keys[i]];
|
|
84
|
+
}
|
|
85
|
+
return tusUpload(base);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Create an upload through the real POST path; return { cap, loc, id }.
|
|
89
|
+
async function _create(tus, mount, headers) {
|
|
90
|
+
var cap = await _drive(tus, _req("POST", mount, Object.assign({ "tus-resumable": VER }, headers || {})));
|
|
91
|
+
var loc = cap.headers.location || "";
|
|
92
|
+
return { cap: cap, loc: loc, id: loc.slice(loc.lastIndexOf("/") + 1) };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function _throws(fn) {
|
|
96
|
+
try { fn(); return null; } catch (e) { return e; }
|
|
97
|
+
}
|
|
98
|
+
async function _rejects(promise) {
|
|
99
|
+
try { await promise; return null; } catch (e) { return e; }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function run() {
|
|
103
|
+
// ---------------------------------------------------------------
|
|
104
|
+
// A. memoryStore(opts) config-time validation
|
|
105
|
+
// ---------------------------------------------------------------
|
|
106
|
+
var eNeg = _throws(function () { return tusUpload.memoryStore({ maxSize: -1 }); });
|
|
107
|
+
check("memoryStore: negative maxSize throws TusError",
|
|
108
|
+
eNeg !== null && eNeg.code === "tus/bad-store-opts");
|
|
109
|
+
var eNaN = _throws(function () { return tusUpload.memoryStore({ maxSize: NaN }); });
|
|
110
|
+
check("memoryStore: NaN maxSize throws", eNaN !== null && eNaN.code === "tus/bad-store-opts");
|
|
111
|
+
var eStr = _throws(function () { return tusUpload.memoryStore({ maxSize: "1024" }); });
|
|
112
|
+
check("memoryStore: string maxSize throws", eStr !== null && eStr.code === "tus/bad-store-opts");
|
|
113
|
+
var eZero = _throws(function () { return tusUpload.memoryStore({ maxSize: 0 }); });
|
|
114
|
+
check("memoryStore: zero maxSize throws", eZero !== null && eZero.code === "tus/bad-store-opts");
|
|
115
|
+
check("memoryStore: undefined maxSize is accepted (unbounded)",
|
|
116
|
+
_throws(function () { return tusUpload.memoryStore({}); }) === null);
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------
|
|
119
|
+
// B. memoryStore method-level rejection branches
|
|
120
|
+
// ---------------------------------------------------------------
|
|
121
|
+
{
|
|
122
|
+
var store = tusUpload.memoryStore({ maxSize: C.BYTES.bytes(16) });
|
|
123
|
+
var rec = await store.create({ length: 10, metadata: {} });
|
|
124
|
+
|
|
125
|
+
var mism = await _rejects(store.append(rec.id, Buffer.from("ab"), 5));
|
|
126
|
+
check("store.append: offset mismatch rejects tus/offset-mismatch",
|
|
127
|
+
mism !== null && mism.code === "tus/offset-mismatch");
|
|
128
|
+
|
|
129
|
+
var overDecl = await _rejects(store.append(rec.id, Buffer.alloc(11), 0));
|
|
130
|
+
check("store.append: exceeding declared Upload-Length rejects tus/length-exceeded",
|
|
131
|
+
overDecl !== null && overDecl.code === "tus/length-exceeded");
|
|
132
|
+
|
|
133
|
+
// Unbounded length but over the store maxSize cap.
|
|
134
|
+
var store2 = tusUpload.memoryStore({ maxSize: C.BYTES.bytes(4) });
|
|
135
|
+
var rec2 = await store2.create({ length: null, deferLength: true, metadata: {} });
|
|
136
|
+
var overMax = await _rejects(store2.append(rec2.id, Buffer.alloc(5), 0));
|
|
137
|
+
check("store.append: exceeding memoryStore maxSize rejects tus/length-exceeded",
|
|
138
|
+
overMax !== null && overMax.code === "tus/length-exceeded");
|
|
139
|
+
|
|
140
|
+
var missing = await _rejects(store.append("no-such-id", Buffer.from("x"), 0));
|
|
141
|
+
check("store.append: unknown id rejects tus/upload-not-found",
|
|
142
|
+
missing !== null && missing.code === "tus/upload-not-found");
|
|
143
|
+
|
|
144
|
+
// setLength on an already-declared upload conflicts.
|
|
145
|
+
var setDup = await _rejects(store.setLength(rec.id, 20));
|
|
146
|
+
check("store.setLength: length already set rejects tus/length-already-set",
|
|
147
|
+
setDup !== null && setDup.code === "tus/length-already-set");
|
|
148
|
+
var setMissing = await _rejects(store.setLength("no-such-id", 5));
|
|
149
|
+
check("store.setLength: unknown id rejects tus/upload-not-found",
|
|
150
|
+
setMissing !== null && setMissing.code === "tus/upload-not-found");
|
|
151
|
+
|
|
152
|
+
// terminate then append/head observe the removal.
|
|
153
|
+
var recT = await store.create({ length: null, deferLength: true, metadata: {} });
|
|
154
|
+
check("store.terminate: existing id resolves true", (await store.terminate(recT.id)) === true);
|
|
155
|
+
check("store.terminate: unknown id resolves false", (await store.terminate("no-such-id")) === false);
|
|
156
|
+
var afterTerm = await _rejects(store.append(recT.id, Buffer.from("x"), 0));
|
|
157
|
+
check("store.append: after terminate rejects tus/upload-not-found",
|
|
158
|
+
afterTerm !== null && afterTerm.code === "tus/upload-not-found");
|
|
159
|
+
check("store.head: after terminate resolves null", (await store.head(recT.id)) === null);
|
|
160
|
+
|
|
161
|
+
// getBuffer for a missing id is null.
|
|
162
|
+
check("store.getBuffer: unknown id resolves null", (await store.getBuffer("no-such-id")) === null);
|
|
163
|
+
|
|
164
|
+
// Expiry: a record whose expireAt is already in the past is swept on
|
|
165
|
+
// head() and reported by purgeExpired().
|
|
166
|
+
var storeE = tusUpload.memoryStore({});
|
|
167
|
+
var recE = await storeE.create({ length: 5, metadata: {}, expirationMs: -1000 });
|
|
168
|
+
check("store.head: expired record resolves null (swept on read)",
|
|
169
|
+
(await storeE.head(recE.id)) === null);
|
|
170
|
+
var recE2 = await storeE.create({ length: 5, metadata: {}, expirationMs: -1000 });
|
|
171
|
+
var purged = await storeE.purgeExpired();
|
|
172
|
+
check("store.purgeExpired: removes expired records", purged >= 1 && recE2.id.length > 0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------
|
|
176
|
+
// C. tusUpload(opts) config-time validation
|
|
177
|
+
// ---------------------------------------------------------------
|
|
178
|
+
var okStore = tusUpload.memoryStore({});
|
|
179
|
+
check("create: non-object opts throws",
|
|
180
|
+
_throws(function () { return tusUpload(null); }) !== null);
|
|
181
|
+
check("create: unknown opt key throws",
|
|
182
|
+
_throws(function () { return tusUpload({ mountPath: "/u", store: okStore, bogusKey: 1 }); }) !== null);
|
|
183
|
+
|
|
184
|
+
var eMount = _throws(function () { return tusUpload({ mountPath: "uploads", store: okStore }); });
|
|
185
|
+
check("create: mountPath without leading '/' throws tus/bad-mountpath",
|
|
186
|
+
eMount !== null && eMount.code === "tus/bad-mountpath");
|
|
187
|
+
check("create: empty mountPath throws",
|
|
188
|
+
_throws(function () { return tusUpload({ mountPath: "", store: okStore }); }) !== null);
|
|
189
|
+
|
|
190
|
+
var eStore = _throws(function () { return tusUpload({ mountPath: "/u", store: { create: function () {} } }); });
|
|
191
|
+
check("create: store missing required methods throws tus/bad-store",
|
|
192
|
+
eStore !== null && eStore.code === "tus/bad-store");
|
|
193
|
+
|
|
194
|
+
var eMax = _throws(function () { return tusUpload({ mountPath: "/u", store: okStore, maxSize: -5 }); });
|
|
195
|
+
check("create: negative maxSize throws tus/bad-opts", eMax !== null && eMax.code === "tus/bad-opts");
|
|
196
|
+
var eChunk = _throws(function () { return tusUpload({ mountPath: "/u", store: okStore, maxChunkSize: 0 }); });
|
|
197
|
+
check("create: zero maxChunkSize throws tus/bad-opts", eChunk !== null && eChunk.code === "tus/bad-opts");
|
|
198
|
+
var eExp = _throws(function () { return tusUpload({ mountPath: "/u", store: okStore, expirationSec: -1 }); });
|
|
199
|
+
check("create: negative expirationSec throws tus/bad-opts", eExp !== null && eExp.code === "tus/bad-opts");
|
|
200
|
+
|
|
201
|
+
var eExt = _throws(function () { return tusUpload({ mountPath: "/u", store: okStore, extensions: ["concatenation"] }); });
|
|
202
|
+
check("create: unknown extension throws tus/bad-opts", eExt !== null && eExt.code === "tus/bad-opts");
|
|
203
|
+
var eAlgo = _throws(function () { return tusUpload({ mountPath: "/u", store: okStore, checksumAlgorithms: ["md5"] }); });
|
|
204
|
+
check("create: unknown checksum algorithm throws tus/bad-opts", eAlgo !== null && eAlgo.code === "tus/bad-opts");
|
|
205
|
+
|
|
206
|
+
check("create: non-function onComplete throws",
|
|
207
|
+
_throws(function () { return tusUpload({ mountPath: "/u", store: okStore, onComplete: 42 }); }) !== null);
|
|
208
|
+
check("create: non-function onTerminate throws",
|
|
209
|
+
_throws(function () { return tusUpload({ mountPath: "/u", store: okStore, onTerminate: "x" }); }) !== null);
|
|
210
|
+
check("create: trailing-slash mountPath is accepted (normalized)",
|
|
211
|
+
_throws(function () { return tusUpload({ mountPath: "/u/", store: okStore }); }) === null);
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------
|
|
214
|
+
// D. routing + Tus-Resumable version gate (§2.2)
|
|
215
|
+
// ---------------------------------------------------------------
|
|
216
|
+
var tus = _mkTus({ store: tusUpload.memoryStore({ maxSize: C.BYTES.mib(1) }), maxSize: C.BYTES.mib(1), checksumAlgorithms: ["sha-256"] });
|
|
217
|
+
|
|
218
|
+
var nm = await _drive(tus, _req("GET", "/somewhere-else"));
|
|
219
|
+
check("route: non-matching path delegates to next()", nm.nextCalled === true && nm.status === null);
|
|
220
|
+
var subpath = await _drive(tus, _req("HEAD", "/uploads/abc/extra", { "tus-resumable": VER }));
|
|
221
|
+
check("route: nested sub-path under a resource delegates to next()", subpath.nextCalled === true);
|
|
222
|
+
|
|
223
|
+
var noVer = await _drive(tus, _req("HEAD", "/uploads/abcdef", {}));
|
|
224
|
+
check("version-gate: missing Tus-Resumable → 412", noVer.status === 412 && noVer.nextCalled === false);
|
|
225
|
+
var badVer = await _drive(tus, _req("HEAD", "/uploads/abcdef", { "tus-resumable": "0.9.0" }));
|
|
226
|
+
check("version-gate: unsupported version → 412 + Tus-Version advertised",
|
|
227
|
+
badVer.status === 412 && badVer.headers["tus-version"] === VER);
|
|
228
|
+
|
|
229
|
+
var opt = await _drive(tus, _req("OPTIONS", "/uploads", {}));
|
|
230
|
+
check("OPTIONS: discovery → 204 with Tus-* headers",
|
|
231
|
+
opt.status === 204 &&
|
|
232
|
+
opt.headers["tus-version"] === VER &&
|
|
233
|
+
typeof opt.headers["tus-extension"] === "string" &&
|
|
234
|
+
opt.headers["tus-max-size"] === String(C.BYTES.mib(1)) &&
|
|
235
|
+
opt.headers["tus-checksum-algorithm"] === "sha-256");
|
|
236
|
+
|
|
237
|
+
// ---------------------------------------------------------------
|
|
238
|
+
// E. POST creation refusals
|
|
239
|
+
// ---------------------------------------------------------------
|
|
240
|
+
var noCreate = _mkTus({ mountPath: "/nc", extensions: ["expiration"] });
|
|
241
|
+
var ncRes = await _drive(noCreate, _req("POST", "/nc", { "tus-resumable": VER, "upload-length": "5" }));
|
|
242
|
+
check("POST: creation extension disabled → 405", ncRes.status === 405);
|
|
243
|
+
|
|
244
|
+
var badLen = await _drive(tus, _req("POST", "/uploads", { "tus-resumable": VER, "upload-length": "abc" }));
|
|
245
|
+
check("POST: non-integer Upload-Length → 400", badLen.status === 400);
|
|
246
|
+
var negLen = await _drive(tus, _req("POST", "/uploads", { "tus-resumable": VER, "upload-length": "-1" }));
|
|
247
|
+
check("POST: negative Upload-Length → 400", negLen.status === 400);
|
|
248
|
+
var junkLen = await _drive(tus, _req("POST", "/uploads", { "tus-resumable": VER, "upload-length": "10abc" }));
|
|
249
|
+
check("POST: trailing-junk Upload-Length → 400", junkLen.status === 400);
|
|
250
|
+
var overLen = await _drive(tus, _req("POST", "/uploads", { "tus-resumable": VER, "upload-length": String(C.BYTES.mib(2)) }));
|
|
251
|
+
check("POST: Upload-Length over Tus-Max-Size → 413", overLen.status === 413);
|
|
252
|
+
var noLen = await _drive(tus, _req("POST", "/uploads", { "tus-resumable": VER }));
|
|
253
|
+
check("POST: neither Upload-Length nor Upload-Defer-Length → 400", noLen.status === 400);
|
|
254
|
+
var badMeta = await _drive(tus, _req("POST", "/uploads",
|
|
255
|
+
{ "tus-resumable": VER, "upload-length": "5", "upload-metadata": "key !!!not-base64!!!" }));
|
|
256
|
+
check("POST: malformed Upload-Metadata → 400", badMeta.status === 400);
|
|
257
|
+
|
|
258
|
+
// store.create failure surfaces as 500 (create.fail metric branch).
|
|
259
|
+
var boomStore = {
|
|
260
|
+
create: function () { return Promise.reject(new Error("disk full")); },
|
|
261
|
+
head: function () { return Promise.resolve(null); },
|
|
262
|
+
append: function () { return Promise.resolve(null); },
|
|
263
|
+
terminate: function () { return Promise.resolve(false); },
|
|
264
|
+
};
|
|
265
|
+
var boomTus = tusUpload({ mountPath: "/boom", store: boomStore, audit: false });
|
|
266
|
+
var boom = await _drive(boomTus, _req("POST", "/boom", { "tus-resumable": VER, "upload-length": "5" }));
|
|
267
|
+
check("POST: store.create rejection → 500", boom.status === 500);
|
|
268
|
+
|
|
269
|
+
// Deferred-length creation succeeds and HEAD reports the defer flag.
|
|
270
|
+
var defCreate = await _create(tus, "/uploads", { "upload-defer-length": "1" });
|
|
271
|
+
check("POST: Upload-Defer-Length:1 → 201", defCreate.cap.status === 201);
|
|
272
|
+
var defHead = await _drive(tus, _req("HEAD", "/uploads/" + defCreate.id, { "tus-resumable": VER }));
|
|
273
|
+
check("HEAD: deferred upload advertises Upload-Defer-Length:1",
|
|
274
|
+
defHead.status === 200 && defHead.headers["upload-defer-length"] === "1");
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------
|
|
277
|
+
// F. HEAD refusals
|
|
278
|
+
// ---------------------------------------------------------------
|
|
279
|
+
var headMiss = await _drive(tus, _req("HEAD", "/uploads/doesnotexist", { "tus-resumable": VER }));
|
|
280
|
+
check("HEAD: unknown upload → 404", headMiss.status === 404);
|
|
281
|
+
|
|
282
|
+
// ---------------------------------------------------------------
|
|
283
|
+
// G. PATCH refusals
|
|
284
|
+
// ---------------------------------------------------------------
|
|
285
|
+
var created = await _create(tus, "/uploads", { "upload-length": "20" });
|
|
286
|
+
|
|
287
|
+
var wrongCt = await _drive(tus, _sreq("PATCH", created.loc,
|
|
288
|
+
{ "tus-resumable": VER, "content-type": "text/plain", "upload-offset": "0" }, "x"));
|
|
289
|
+
check("PATCH: wrong Content-Type → 415", wrongCt.status === 415);
|
|
290
|
+
|
|
291
|
+
var noOffset = await _drive(tus, _sreq("PATCH", created.loc,
|
|
292
|
+
{ "tus-resumable": VER, "content-type": OCTET }, "x"));
|
|
293
|
+
check("PATCH: missing Upload-Offset → 400", noOffset.status === 400);
|
|
294
|
+
var badOffset = await _drive(tus, _sreq("PATCH", created.loc,
|
|
295
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "-3" }, "x"));
|
|
296
|
+
check("PATCH: negative Upload-Offset → 400", badOffset.status === 400);
|
|
297
|
+
var junkOffset = await _drive(tus, _sreq("PATCH", created.loc,
|
|
298
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0x4" }, "x"));
|
|
299
|
+
check("PATCH: non-integer Upload-Offset → 400", junkOffset.status === 400);
|
|
300
|
+
|
|
301
|
+
var patchMiss = await _drive(tus, _sreq("PATCH", "/uploads/doesnotexist",
|
|
302
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0" }, "x"));
|
|
303
|
+
check("PATCH: unknown upload → 404", patchMiss.status === 404);
|
|
304
|
+
|
|
305
|
+
var offMismatch = await _drive(tus, _sreq("PATCH", created.loc,
|
|
306
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "7" }, "x"));
|
|
307
|
+
check("PATCH: Upload-Offset not at head → 409", offMismatch.status === 409);
|
|
308
|
+
|
|
309
|
+
// checksum extension disabled but header present.
|
|
310
|
+
var noChk = _mkTus({ mountPath: "/nochk", extensions: ["creation", "termination"] });
|
|
311
|
+
var ncCreated = await _create(noChk, "/nochk", { "upload-length": "4" });
|
|
312
|
+
var chkDisabled = await _drive(noChk, _sreq("PATCH", ncCreated.loc,
|
|
313
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0", "upload-checksum": "sha-256 AAAA" }, "abcd"));
|
|
314
|
+
check("PATCH: Upload-Checksum with checksum ext disabled → 400", chkDisabled.status === 400);
|
|
315
|
+
|
|
316
|
+
var chkMalformed = await _drive(tus, _sreq("PATCH", created.loc,
|
|
317
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0", "upload-checksum": "sha-256" }, "abcd"));
|
|
318
|
+
check("PATCH: malformed Upload-Checksum (no digest) → 400", chkMalformed.status === 400);
|
|
319
|
+
var chkUnsupported = await _drive(tus, _sreq("PATCH", created.loc,
|
|
320
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0", "upload-checksum": "sha-512 AAAA" }, "abcd"));
|
|
321
|
+
check("PATCH: unsupported checksum algorithm → 400", chkUnsupported.status === 400);
|
|
322
|
+
var chkMismatch = await _drive(tus, _sreq("PATCH", created.loc,
|
|
323
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0", "upload-checksum": "sha-256 AAAA" }, "abcd"));
|
|
324
|
+
check("PATCH: checksum mismatch → 460 (tus.io §3.5)", chkMismatch.status === 460);
|
|
325
|
+
|
|
326
|
+
// BUG (reported): an oversized chunk is refused fail-closed — no bytes
|
|
327
|
+
// written — but with the wrong status/body (400 + leaked "tus/chunk-too
|
|
328
|
+
// -large" instead of 413). Assert only the invariant that holds today
|
|
329
|
+
// AND after a fix: the request is rejected and the offset does not move.
|
|
330
|
+
var smallChunkTus = _mkTus({ mountPath: "/tiny", maxChunkSize: C.BYTES.bytes(8) });
|
|
331
|
+
var tinyCreated = await _create(smallChunkTus, "/tiny", { "upload-length": "100" });
|
|
332
|
+
var oversized = await _drive(smallChunkTus, _sreq("PATCH", tinyCreated.loc,
|
|
333
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0" }, Buffer.alloc(20)));
|
|
334
|
+
check("PATCH: oversized chunk is refused (status >= 400)", oversized.status >= 400);
|
|
335
|
+
var afterOversized = await _drive(smallChunkTus, _req("HEAD", tinyCreated.loc, { "tus-resumable": VER }));
|
|
336
|
+
check("PATCH: oversized chunk wrote no bytes (offset unchanged at 0)",
|
|
337
|
+
afterOversized.headers["upload-offset"] === "0");
|
|
338
|
+
|
|
339
|
+
// BUG (reported): a prototype key in Upload-Checksum bypasses the
|
|
340
|
+
// allow-set lookup and reaches createHash(<object>) → the request is
|
|
341
|
+
// still refused fail-closed (no append), but as a 500 rather than a
|
|
342
|
+
// clean 400. Assert only the fail-closed invariant.
|
|
343
|
+
var protoChk = await _drive(tus, _sreq("PATCH", created.loc,
|
|
344
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0", "upload-checksum": "__proto__ AAAA" }, "abcd"));
|
|
345
|
+
check("PATCH: prototype-key Upload-Checksum is refused (status >= 400)", protoChk.status >= 400);
|
|
346
|
+
var afterProto = await _drive(tus, _req("HEAD", created.loc, { "tus-resumable": VER }));
|
|
347
|
+
check("PATCH: prototype-key checksum wrote no bytes (offset unchanged at 0)",
|
|
348
|
+
afterProto.headers["upload-offset"] === "0");
|
|
349
|
+
|
|
350
|
+
// deferred-length finalization on PATCH: negative + over-max are
|
|
351
|
+
// refused; a valid declaration finalizes and HEAD reports the length.
|
|
352
|
+
var defTus = _mkTus({ mountPath: "/def", store: tusUpload.memoryStore({ maxSize: C.BYTES.bytes(64) }), maxSize: C.BYTES.bytes(64) });
|
|
353
|
+
var defA = await _create(defTus, "/def", { "upload-defer-length": "1" });
|
|
354
|
+
var defNeg = await _drive(defTus, _sreq("PATCH", defA.loc,
|
|
355
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0", "upload-length": "-5" }, "hi"));
|
|
356
|
+
check("PATCH: deferred finalization with negative Upload-Length → 400", defNeg.status === 400);
|
|
357
|
+
var defB = await _create(defTus, "/def", { "upload-defer-length": "1" });
|
|
358
|
+
var defOver = await _drive(defTus, _sreq("PATCH", defB.loc,
|
|
359
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0", "upload-length": String(C.BYTES.bytes(65)) }, "hi"));
|
|
360
|
+
check("PATCH: deferred finalization over Tus-Max-Size → 413", defOver.status === 413);
|
|
361
|
+
var defC = await _create(defTus, "/def", { "upload-defer-length": "1" });
|
|
362
|
+
var defOk = await _drive(defTus, _sreq("PATCH", defC.loc,
|
|
363
|
+
{ "tus-resumable": VER, "content-type": OCTET, "upload-offset": "0", "upload-length": "10" }, "hi"));
|
|
364
|
+
check("PATCH: deferred finalization with valid Upload-Length → 204", defOk.status === 204);
|
|
365
|
+
var defHead2 = await _drive(defTus, _req("HEAD", defC.loc, { "tus-resumable": VER }));
|
|
366
|
+
check("HEAD: after deferred finalization Upload-Length is reported",
|
|
367
|
+
defHead2.headers["upload-length"] === "10");
|
|
368
|
+
|
|
369
|
+
// ---------------------------------------------------------------
|
|
370
|
+
// H. DELETE refusals
|
|
371
|
+
// ---------------------------------------------------------------
|
|
372
|
+
var noTerm = _mkTus({ mountPath: "/noterm", extensions: ["creation"] });
|
|
373
|
+
var ntCreated = await _create(noTerm, "/noterm", { "upload-length": "4" });
|
|
374
|
+
var termDisabled = await _drive(noTerm, _req("DELETE", ntCreated.loc, { "tus-resumable": VER }));
|
|
375
|
+
check("DELETE: termination extension disabled → 405", termDisabled.status === 405);
|
|
376
|
+
|
|
377
|
+
var delMiss = await _drive(tus, _req("DELETE", "/uploads/doesnotexist", { "tus-resumable": VER }));
|
|
378
|
+
check("DELETE: unknown upload → 404", delMiss.status === 404);
|
|
379
|
+
var delOk = await _drive(tus, _req("DELETE", created.loc, { "tus-resumable": VER }));
|
|
380
|
+
check("DELETE: existing upload → 204", delOk.status === 204);
|
|
381
|
+
var delAgain = await _drive(tus, _req("DELETE", created.loc, { "tus-resumable": VER }));
|
|
382
|
+
check("DELETE: second delete of same id → 404 (already gone)", delAgain.status === 404);
|
|
383
|
+
|
|
384
|
+
// ---------------------------------------------------------------
|
|
385
|
+
// I. method-not-allowed fall-through
|
|
386
|
+
// ---------------------------------------------------------------
|
|
387
|
+
var putColl = await _drive(tus, _req("PUT", "/uploads", { "tus-resumable": VER }));
|
|
388
|
+
check("405: PUT on collection → Allow: OPTIONS, POST",
|
|
389
|
+
putColl.status === 405 && putColl.headers.allow === "OPTIONS, POST");
|
|
390
|
+
var postRes = await _drive(tus, _req("POST", "/uploads/abcdef", { "tus-resumable": VER }));
|
|
391
|
+
check("405: POST on resource → Allow includes DELETE",
|
|
392
|
+
postRes.status === 405 && /DELETE/.test(postRes.headers.allow));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
module.exports = { run: run };
|
|
396
|
+
|
|
397
|
+
if (require.main === module) {
|
|
398
|
+
run().then(
|
|
399
|
+
function () { console.log("OK — " + helpers.getChecks() + " checks passed"); },
|
|
400
|
+
function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
|
|
401
|
+
);
|
|
402
|
+
}
|