jsql-neo 6.5.1 → 6.5.4
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/bin/jsql +23 -11
- package/index.js +1 -1
- package/lib/star-nag.js +44 -8
- package/package.json +3 -2
- package/test/star-nag.test.js +160 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **One engine to rule them all** — a Rust-powered embedded database that speaks your language:
|
|
4
4
|
> MySQL, PostgreSQL, MongoDB, Redis, SQL, TypeScript, **and the browser**.
|
|
5
5
|
|
|
6
|
-
> **v6.5.
|
|
6
|
+
> **v6.5.4** — stable release · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo) · [jsql.vexify.top](https://jsql.vexify.top)
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|

|
package/bin/jsql
CHANGED
|
@@ -201,28 +201,40 @@ const cli = yaggs({ pkg: require('../package.json') })
|
|
|
201
201
|
if (argv.reset) sn.setStarred(false);
|
|
202
202
|
const c = sn.readConfig();
|
|
203
203
|
if (c.starred) {
|
|
204
|
-
console.log(
|
|
205
|
-
console.log('Run with --reset to re-check
|
|
204
|
+
console.log(`Already confirmed starred 已确认过: ${sn.configPath()}`);
|
|
205
|
+
console.log('Run with --reset to re-check · 用 --reset 重新检查');
|
|
206
206
|
return;
|
|
207
207
|
}
|
|
208
|
-
console.log('Opening browser to confirm your star…');
|
|
208
|
+
console.log('Opening browser to confirm your star… 正在打开浏览器确认');
|
|
209
209
|
const nonceUrl = sn.startOauthCheck(); // 打开浏览器 + 后台轮询写回 ~/.jsqlmr
|
|
210
210
|
if (!process.stdout.isTTY) console.log(' ' + nonceUrl);
|
|
211
|
-
console.log(
|
|
212
|
-
console.log('Waiting for authorization (up to 3 minutes)…');
|
|
211
|
+
console.log(`Authorization updates this config 授权后写入配置: ${sn.configPath()}`);
|
|
212
|
+
console.log('Waiting for authorization (up to 3 minutes)… 等待授权(最多 3 分钟)…');
|
|
213
213
|
// startOauthCheck 的定时器是 unref 的,命令必须自己前台等,否则接不到结果
|
|
214
214
|
const res = sn.waitForResult(nonceUrl, 180000);
|
|
215
|
-
|
|
215
|
+
const status = res && res.status;
|
|
216
|
+
if (status === 'starred') {
|
|
216
217
|
sn.setStarred(true);
|
|
217
|
-
console.log(`Confirmed: ${res.login}
|
|
218
|
+
console.log(`Confirmed: ${res.login} starred ${res.repo} — thanks! 已确认,谢谢!`);
|
|
218
219
|
return;
|
|
219
220
|
}
|
|
220
|
-
if (
|
|
221
|
-
console.log(`
|
|
222
|
-
console.log(`Star it at
|
|
221
|
+
if (status === 'not-starred') {
|
|
222
|
+
console.log(`Not starred yet 还没 star: ${res.login} has not starred ${res.repo}`);
|
|
223
|
+
console.log(`Star it at ${sn.URL}, then run \`jsql star\` again · 点星后重跑 \`jsql star\``);
|
|
223
224
|
return;
|
|
224
225
|
}
|
|
225
|
-
|
|
226
|
+
if (status === 'denied' || status === 'error') {
|
|
227
|
+
// 这不是「没 star」,是授权本身没走通(code 过期、链接被重复打开、被拒绝)
|
|
228
|
+
const why = status === 'denied' ? 'declined 被拒绝' : 'failed 失败';
|
|
229
|
+
console.log(`Authorization ${why}: ${res.error || 'unknown reason'}`);
|
|
230
|
+
console.log('The link is single-use — run `jsql star` again · 链接一次性,请重跑 `jsql star`');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (status === 'expired') {
|
|
234
|
+
console.log('The link expired 链接已过期 — run `jsql star` again · 请重跑');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
console.log('Timed out 已超时 — run `jsql star` again · 请重跑');
|
|
226
238
|
})
|
|
227
239
|
.command('bench', 'Insert + query benchmark against a data directory', (sub) => {
|
|
228
240
|
sub.option('n', { type: 'number', description: 'Number of rows (default 10000)' });
|
package/index.js
CHANGED
package/lib/star-nag.js
CHANGED
|
@@ -35,6 +35,24 @@ const AUTH_RESULT = AUTH_BASE + '/result.php';
|
|
|
35
35
|
* 拿到结果写进 ~/.jsqlmr(starred: true/false)。
|
|
36
36
|
* 进程提前退出就放弃这一次,下次启动会再提示(30 天节流管频率)。
|
|
37
37
|
*/
|
|
38
|
+
// result.php 的合法终态:done / denied / error。
|
|
39
|
+
// 只有终态才写配置——"started" 这一类中间态写进去只会污染 ~/.jsqlmr。
|
|
40
|
+
const TERMINAL_STATES = new Set(['done', 'denied', 'error']);
|
|
41
|
+
|
|
42
|
+
// 把 result.php 的响应收敛成规范状态。老服务端会把 started 也报成 done,
|
|
43
|
+
// 缺少 starred 字段 —— 一律当未完成处理,让调用方继续等。
|
|
44
|
+
function parseAuthResult(j) {
|
|
45
|
+
if (!j || typeof j !== 'object') return null;
|
|
46
|
+
const st = j.state;
|
|
47
|
+
if (!TERMINAL_STATES.has(st)) return null;
|
|
48
|
+
const r = j.result || {};
|
|
49
|
+
if (st === 'done') {
|
|
50
|
+
if (typeof r.starred !== 'boolean') return null;
|
|
51
|
+
return { status: r.starred ? 'starred' : 'not-starred', login: r.login, repo: r.repo };
|
|
52
|
+
}
|
|
53
|
+
return { status: st, error: r.error || (st === 'denied' ? 'authorization denied' : 'authorization failed') };
|
|
54
|
+
}
|
|
55
|
+
|
|
38
56
|
function startOauthCheck() {
|
|
39
57
|
const crypto = require('crypto');
|
|
40
58
|
const nonce = crypto.randomBytes(24).toString('base64url');
|
|
@@ -61,12 +79,13 @@ function startOauthCheck() {
|
|
|
61
79
|
if (err) return;
|
|
62
80
|
let j = null;
|
|
63
81
|
try { j = JSON.parse(stdout); } catch (e) { return; }
|
|
64
|
-
|
|
82
|
+
const res = parseAuthResult(j);
|
|
83
|
+
if (!res) return; // pending / started / 格式不符 —— 继续等
|
|
65
84
|
clearInterval(t);
|
|
66
85
|
const cfg = readConfig();
|
|
67
86
|
cfg.oauthCheckedAt = Date.now();
|
|
68
|
-
cfg.login =
|
|
69
|
-
cfg.starred =
|
|
87
|
+
cfg.login = res.login || undefined;
|
|
88
|
+
cfg.starred = res.status === 'starred';
|
|
70
89
|
if (cfg.starred) cfg.starredAt = Date.now();
|
|
71
90
|
writeConfig(cfg);
|
|
72
91
|
});
|
|
@@ -189,7 +208,7 @@ function nag(opts = {}) {
|
|
|
189
208
|
}
|
|
190
209
|
|
|
191
210
|
out.write(`\n If JSQL-NEO saved you time, a star helps others find it: ${URL}\n`);
|
|
192
|
-
out.write(`
|
|
211
|
+
out.write(` 如果帮到了你,点个 star;运行 \`jsql star\` 确认 · JSQL_NO_STAR_NAG=1 关闭提示\n\n`);
|
|
193
212
|
|
|
194
213
|
// 只记录「有没有 star」,不再写 lastNagAt —— 没有节流,写它没有意义。
|
|
195
214
|
return true;
|
|
@@ -214,6 +233,21 @@ function setStarred(v) {
|
|
|
214
233
|
* @param {number} [timeoutMs] 最长等待时间,默认 180 秒
|
|
215
234
|
* @returns {object|null} result 对象(含 login/starred/repo),超时或失败返回 null
|
|
216
235
|
*/
|
|
236
|
+
/**
|
|
237
|
+
* 前台等待授权结果(`jsql star` 用)。
|
|
238
|
+
*
|
|
239
|
+
* 返回规范化后的结果对象:
|
|
240
|
+
* { status: 'starred', login, repo } 已 star
|
|
241
|
+
* { status: 'not-starred', login, repo } 授权完成但没 star
|
|
242
|
+
* { status: 'denied', error } 用户在 GitHub 拒绝授权
|
|
243
|
+
* { status: 'error', error } 回调失败(code 过期/复用等)
|
|
244
|
+
* { status: 'expired' } 链接超龄被清理
|
|
245
|
+
* null 超时(timeoutMs 内没有终态)
|
|
246
|
+
*
|
|
247
|
+
* 老服务端会把 "started" 这类中间态也报成 done 且不带 starred 字段 ——
|
|
248
|
+
* parseAuthResult 一律判为未完成,继续轮询直到超时,而不是打印一段
|
|
249
|
+
* login/repo 全为 undefined 的假失败信息。
|
|
250
|
+
*/
|
|
217
251
|
function waitForResult(startUrl, timeoutMs = 180000) {
|
|
218
252
|
const { execFileSync } = require('child_process');
|
|
219
253
|
// 注意:本模块顶部有 const URL = 'https://github.com/...' 会遮蔽全局 URL 类,
|
|
@@ -232,9 +266,11 @@ function waitForResult(startUrl, timeoutMs = 180000) {
|
|
|
232
266
|
try {
|
|
233
267
|
const out = execFileSync('curl', ['-sS', '-m', '5', AUTH_RESULT + '?nonce=' + nonce],
|
|
234
268
|
{ stdio: ['ignore', 'pipe', 'ignore'], timeout: 8000, encoding: 'utf8' });
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
269
|
+
let j = null;
|
|
270
|
+
try { j = JSON.parse(out); } catch (e) { j = null; }
|
|
271
|
+
const res = parseAuthResult(j);
|
|
272
|
+
if (res) return res;
|
|
273
|
+
if (j && j.state === 'expired') return { status: 'expired' };
|
|
238
274
|
} catch (e) {
|
|
239
275
|
// 网络抖动 / 超时 —— 继续等
|
|
240
276
|
}
|
|
@@ -243,4 +279,4 @@ function waitForResult(startUrl, timeoutMs = 180000) {
|
|
|
243
279
|
return null;
|
|
244
280
|
}
|
|
245
281
|
|
|
246
|
-
module.exports = { nag, checkStarred, startOauthCheck, waitForResult, readConfig, setStarred, configPath, REPO, URL };
|
|
282
|
+
module.exports = { nag, checkStarred, startOauthCheck, waitForResult, parseAuthResult, readConfig, setStarred, configPath, REPO, URL };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsql-neo",
|
|
3
|
-
"version": "6.5.
|
|
3
|
+
"version": "6.5.4",
|
|
4
4
|
"description": "JSQL-NEO — Rust-powered embedded database with WASM, REST API, B-Tree indexes, WAL, crash recovery",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
],
|
|
47
47
|
"scripts": {
|
|
48
48
|
"postinstall": "node postinstall.js",
|
|
49
|
-
"test": "node test/smoke.js && node test/native.test.js && node test/json-functions.test.js && node test/sql-parser.test.js && node test/readme-audit.test.js && node test/query-opt.test.js && node test/wal-recovery.test.js && node test/mysql-compat-hijack.test.js && node test/dml-constraints.test.js",
|
|
49
|
+
"test": "node test/smoke.js && node test/native.test.js && node test/json-functions.test.js && node test/sql-parser.test.js && node test/readme-audit.test.js && node test/query-opt.test.js && node test/wal-recovery.test.js && node test/mysql-compat-hijack.test.js && node test/dml-constraints.test.js && node test/star-nag.test.js",
|
|
50
50
|
"test:parser": "node test/sql-parser.test.js",
|
|
51
51
|
"test:btree": "node test/btree.test.js",
|
|
52
52
|
"test:native": "node test/native.test.js",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"test:index": "node test/index-ddl.test.js",
|
|
60
60
|
"test:mongo": "node test/mongo-group.test.js",
|
|
61
61
|
"test:multi": "node test/multiprotocol.test.js",
|
|
62
|
+
"test:star": "node test/star-nag.test.js",
|
|
62
63
|
"test:savepoint": "node test/savepoint.test.js",
|
|
63
64
|
"test:alter": "node test/alter-table.test.js",
|
|
64
65
|
"test:ident": "node test/identifier-keywords.test.js",
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jsql star 授权流回归测试
|
|
3
|
+
*
|
|
4
|
+
* 背景:线上 result.php 曾把「state 文件存在」一律当成授权完成,
|
|
5
|
+
* 而 start.php 一开始就会写入 {"state":"started"}。于是 CLI 第二轮轮询
|
|
6
|
+
* 就拿到 started 当 done,打印出:
|
|
7
|
+
* Authorization finished, but undefined has not starred undefined yet.
|
|
8
|
+
* Star it at https://github.com/undefined then run `jsql star` again.
|
|
9
|
+
* 真相是授权根本没走完。
|
|
10
|
+
*
|
|
11
|
+
* 覆盖:
|
|
12
|
+
* 1. parseAuthResult 对各形态响应的归一化(中间态不得当成完成)
|
|
13
|
+
* 2. CLI 对 starred / not-starred / denied / error 的输出
|
|
14
|
+
* 3. 慢用户场景:先 pending 再完成,CLI 必须继续等而不是误判
|
|
15
|
+
* 4. waitForResult 的超时与 expired 收敛
|
|
16
|
+
*
|
|
17
|
+
* 注意:AUTH_RESULT 是 lib/star-nag 在 require 时从 JSQL_AUTH_BASE 读的,
|
|
18
|
+
* 所以必须先把 env 设好再 require(waitForResult 又用同步 curl,
|
|
19
|
+
* 本进程内的 server 会被饿死,所以 CLI 相关用例一律起子进程)。
|
|
20
|
+
*/
|
|
21
|
+
const http = require('http');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const os = require('os');
|
|
25
|
+
const { spawn } = require('child_process');
|
|
26
|
+
|
|
27
|
+
const PORT = Number(process.env.JSQL_STAR_TEST_PORT) || 34711;
|
|
28
|
+
process.env.JSQL_AUTH_BASE = `http://127.0.0.1:${PORT}`;
|
|
29
|
+
|
|
30
|
+
const sn = require('../lib/star-nag');
|
|
31
|
+
|
|
32
|
+
let pass = 0, fail = 0;
|
|
33
|
+
function ok(name, cond, extra) {
|
|
34
|
+
if (cond) { pass++; console.log('[OK] ' + name); }
|
|
35
|
+
else { fail++; console.log('[FAIL] ' + name + (extra !== undefined ? ' → ' + JSON.stringify(extra) : '')); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 当前 result.php 要返回的内容;pollsLeft <= 0 时切到 after */
|
|
39
|
+
let scenario = { state: 'pending' };
|
|
40
|
+
let pollsLeft = 0;
|
|
41
|
+
let after = null;
|
|
42
|
+
|
|
43
|
+
const server = http.createServer((req, res) => {
|
|
44
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
45
|
+
if (new URL(req.url, 'http://127.0.0.1').pathname === '/result.php') {
|
|
46
|
+
if (pollsLeft > 0 && --pollsLeft === 0 && after) scenario = after;
|
|
47
|
+
res.end(JSON.stringify(scenario));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
res.end('{}');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function runCli(mrPath) {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const bin = path.join(__dirname, '..', 'bin', 'jsql');
|
|
56
|
+
const child = spawn(process.execPath, [bin, 'star'], {
|
|
57
|
+
env: { ...process.env, JSQL_AUTH_BASE: `http://127.0.0.1:${PORT}`, JSQL_MR_PATH: mrPath, JSQL_NO_STAR_NAG: '1' },
|
|
58
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
59
|
+
});
|
|
60
|
+
let out = '';
|
|
61
|
+
child.stdout.on('data', (c) => { out += c; });
|
|
62
|
+
child.stderr.on('data', (c) => { out += c; });
|
|
63
|
+
const t = setTimeout(() => { try { child.kill(); } catch (e) {} resolve(out + '\n[TIMEOUT]'); }, 40000);
|
|
64
|
+
child.on('exit', () => { clearTimeout(t); resolve(out); });
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function main() {
|
|
69
|
+
/* ---------- 1. parseAuthResult ---------- */
|
|
70
|
+
console.log('--- parseAuthResult:中间态不得当成完成 ---');
|
|
71
|
+
{
|
|
72
|
+
const P = sn.parseAuthResult;
|
|
73
|
+
ok('done + starred=true → starred', P({ state: 'done', result: { starred: true, login: 'u', repo: 'o/r' } }).status === 'starred');
|
|
74
|
+
ok('done + starred=false → not-starred', P({ state: 'done', result: { starred: false, login: 'u', repo: 'o/r' } }).status === 'not-starred');
|
|
75
|
+
ok('started 被误报成 done → 未完成', P({ state: 'done', result: { state: 'started', at: 1 } }) === null);
|
|
76
|
+
ok('pending → 未完成', P({ state: 'pending' }) === null);
|
|
77
|
+
ok('缺 result 的 done → 未完成', P({ state: 'done' }) === null);
|
|
78
|
+
ok('denied → 带原因', P({ state: 'denied', result: { error: 'access_denied' } }).status === 'denied');
|
|
79
|
+
ok('error → 带原因', P({ state: 'error', result: { error: 'The code passed is incorrect or expired.' } }).error === 'The code passed is incorrect or expired.');
|
|
80
|
+
ok('空响应 → 未完成', P(null) === null && P({}) === null);
|
|
81
|
+
ok('未知状态 → 未完成', P({ state: 'weird' }) === null);
|
|
82
|
+
ok('expired 不在终态集(由 waitForResult 单独处理)', P({ state: 'expired' }) === null);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await new Promise((r) => server.listen(PORT, '127.0.0.1', r));
|
|
86
|
+
const mrDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsqlmr-'));
|
|
87
|
+
|
|
88
|
+
/* ---------- 2. CLI 各终态输出 ---------- */
|
|
89
|
+
console.log('\n--- 假 result.php 驱动真实 CLI ---');
|
|
90
|
+
const cases = [
|
|
91
|
+
['done + starred', { state: 'done', result: { starred: true, login: 'octocat', repo: 'vexify-org/JSQL-neo' } },
|
|
92
|
+
'Confirmed: octocat starred vexify-org/JSQL-neo'],
|
|
93
|
+
['done + 未 star', { state: 'done', result: { starred: false, login: 'octocat', repo: 'vexify-org/JSQL-neo' } },
|
|
94
|
+
'Not starred yet 还没 star: octocat has not starred vexify-org/JSQL-neo'],
|
|
95
|
+
['denied', { state: 'denied', result: { error: 'access_denied' } },
|
|
96
|
+
'Authorization declined 被拒绝: access_denied'],
|
|
97
|
+
['error(code 过期/复用)', { state: 'error', result: { error: 'The code passed is incorrect or expired.' } },
|
|
98
|
+
'Authorization failed 失败: The code passed is incorrect or expired.'],
|
|
99
|
+
];
|
|
100
|
+
for (const [name, resp, expect] of cases) {
|
|
101
|
+
scenario = resp; pollsLeft = 0; after = null;
|
|
102
|
+
const out = await runCli(path.join(mrDir, name.replace(/[^a-z0-9]+/gi, '-')));
|
|
103
|
+
ok(`CLI 输出正确:${name}`, out.includes(expect), out.trim().slice(0, 200));
|
|
104
|
+
ok(`CLI 不再出现 undefined:${name}`, !/undefined/.test(out), out.trim().slice(0, 200));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* ---------- 3. 慢用户 ---------- */
|
|
108
|
+
console.log('\n--- 慢用户:先 pending 再完成,CLI 必须继续等而不是误判 ---');
|
|
109
|
+
{
|
|
110
|
+
scenario = { state: 'pending' };
|
|
111
|
+
pollsLeft = 2;
|
|
112
|
+
after = { state: 'done', result: { starred: true, login: 'octocat', repo: 'vexify-org/JSQL-neo' } };
|
|
113
|
+
const out = await runCli(path.join(mrDir, 'slow'));
|
|
114
|
+
ok('等完 pending 后正确确认', out.includes('Confirmed: octocat starred vexify-org/JSQL-neo'), out.trim().slice(0, 200));
|
|
115
|
+
ok('全程没有误判成完成', !/Not starred yet/.test(out), out.trim().slice(0, 200));
|
|
116
|
+
ok('全程没有 undefined', !/undefined/.test(out), out.trim().slice(0, 200));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ---------- 4. waitForResult ---------- */
|
|
120
|
+
console.log('\n--- waitForResult:中间态与超时 ---');
|
|
121
|
+
{
|
|
122
|
+
// waitForResult 用同步 curl,会阻塞本进程事件循环 —— server 必须留在父进程,
|
|
123
|
+
// 轮询放到子进程里跑,否则 parent 的 server 被饿死,curl 全部 -m 超时。
|
|
124
|
+
const url = `http://127.0.0.1:${PORT}/start.php?nonce=abcdefghijklmnopqrst`;
|
|
125
|
+
const poll = (resp, timeoutMs) => new Promise((resolve) => {
|
|
126
|
+
scenario = resp; pollsLeft = 0; after = null;
|
|
127
|
+
const code = [
|
|
128
|
+
`const sn = require(${JSON.stringify(path.join(__dirname, '..', 'lib', 'star-nag'))});`,
|
|
129
|
+
`const t = Date.now();`,
|
|
130
|
+
`const r = sn.waitForResult(process.env.TEST_URL, ${timeoutMs});`,
|
|
131
|
+
`process.stdout.write(JSON.stringify({ r, ms: Date.now() - t }));`,
|
|
132
|
+
].join('\n');
|
|
133
|
+
const c = spawn(process.execPath, ['-e', code],
|
|
134
|
+
{ env: { ...process.env, TEST_URL: url }, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
135
|
+
let out = '';
|
|
136
|
+
c.stdout.on('data', (d) => { out += d; });
|
|
137
|
+
c.on('exit', () => { try { resolve(JSON.parse(out)); } catch (e) { resolve({ r: null, ms: -1 }); } });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
for (const [name, resp] of [
|
|
141
|
+
['pending', { state: 'pending' }],
|
|
142
|
+
['老服务端误报 started 为 done', { state: 'done', result: { state: 'started', at: 1 } }],
|
|
143
|
+
]) {
|
|
144
|
+
const { r, ms } = await poll(resp, 3000);
|
|
145
|
+
ok(`waitForResult(${name}) 超时返回 null`, r === null, r);
|
|
146
|
+
ok(`确实等满了 3 秒`, ms >= 2800, ms);
|
|
147
|
+
}
|
|
148
|
+
{
|
|
149
|
+
const { r, ms } = await poll({ state: 'expired' }, 5000);
|
|
150
|
+
ok('waitForResult(expired) 立即收敛', !!r && r.status === 'expired', r);
|
|
151
|
+
ok('expired 没有拖到超时', ms >= 0 && ms < 2500, ms);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
server.close();
|
|
156
|
+
console.log(`\nRESULT: ${pass} passed, ${fail} failed`);
|
|
157
|
+
if (fail > 0) process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
main().catch((e) => { console.error(e); process.exit(1); });
|