dsh-ris-yds 0.0.0-stage → 1.0.0
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/LICENSE +21 -0
- package/README.md +141 -2
- package/assets/yds-chrome-resource-override.crx +0 -0
- package/bin/check-listing.mjs +247 -0
- package/bin/yds.js +19 -0
- package/cordis.patch.yml +13 -0
- package/lib/browser.js +428 -0
- package/lib/build.js +115 -0
- package/lib/cli.js +1003 -0
- package/lib/confcenter.js +377 -0
- package/lib/config.js +552 -0
- package/lib/fe-dev.js +206 -0
- package/lib/ide-proxy.js +682 -0
- package/lib/ide-watch.js +188 -0
- package/lib/ide.js +702 -0
- package/lib/index.js +267 -0
- package/lib/socks.js +260 -0
- package/lib/token.js +36 -0
- package/lib/yms.js +302 -0
- package/lib/yoncode.js +249 -0
- package/native/yds-rust.darwin-arm64.node +0 -0
- package/native/yds-rust.darwin-x64.node +0 -0
- package/native/yds-rust.linux-arm64.node +0 -0
- package/native/yds-rust.linux-x64.node +0 -0
- package/native/yds-rust.win32-arm64.node +0 -0
- package/native/yds-rust.win32-x64.node +0 -0
- package/package.json +58 -4
- package/skills/dsh-ris-yds/SKILL.md +134 -0
- package/test/browser.test.mjs +442 -0
- package/test/cli.test.mjs +246 -0
- package/test/config.test.mjs +106 -0
- package/test/fe-dev.test.mjs +93 -0
- package/test/ide-config.test.mjs +151 -0
- package/test/ide-proxy.test.mjs +194 -0
- package/test/ide-watch.test.mjs +79 -0
- package/test/launch-proxy.test.mjs +83 -0
- package/test/mode-separation.test.mjs +147 -0
- package/test/proxy-standalone.test.mjs +118 -0
- package/test/yoncode.test.mjs +159 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dsh-ris-yds
|
|
3
|
+
description: How to run the YonBIP YDS backend locally with the dsh-ris-yds tools — config resolution (settings.json + optional .env), YYCToken, confcenter sync, SOCKS forward, yms-home build/start/stop, and ris_dev JDWP debugging. Use when asked to start/stop/debug the yds backend, generate YYCToken, pull confcenter config, or wire the YMS program into dsh-ris-ide.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dsh-ris-yds — YonBIP YDS 本地运行时
|
|
7
|
+
|
|
8
|
+
本地跑 `c-ass-kk-hans`(YDS 客)后端 = 在 yms-home 里起一个 YMS(WebSphere Liberty)容器,
|
|
9
|
+
应用 WAR 部署在容器里。所有机器相关值来自配置文件,本 skill 不依赖任何写死的 IP。
|
|
10
|
+
|
|
11
|
+
## 配置模型(动手前先 `yds_doctor`)
|
|
12
|
+
|
|
13
|
+
| 来源 | 文件 | 性质 |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| 项目配置 | `<project>/.vscode/settings.json`(yds.* 键) | **只读**(YonCode 标准格式,共用工作区不破坏) |
|
|
16
|
+
| 机器配置 | `<workspace>/.env`(可选,KEY=VALUE) | SOCKS 代理、git 凭据、本机端口覆盖 |
|
|
17
|
+
| 环境变量 | `YDS_SOCKS_HOST` 等 | 临时覆盖 |
|
|
18
|
+
|
|
19
|
+
关键键:`yds.workbenchUrl / accessKey / accessSecret / serverBindAddress / serverPort / env`(项目);
|
|
20
|
+
`socks.host/port`(SOCKS 存在→DB/Redis 走本地转发;不存在→直连)。本地监听端口**自动选空闲端口**(从回调端口 +2 起找),不需要配置。
|
|
21
|
+
回调地址 = serverBindAddress:serverPort;本地监听端口默认=serverPort,WSL mirrored/端口冲突时用
|
|
22
|
+
本地端口自动错开,无需配置(Windows 侧可用 portproxy 把回调端口映射回来)。
|
|
23
|
+
|
|
24
|
+
## 标准流程(新环境从零)
|
|
25
|
+
|
|
26
|
+
1. `yds_doctor` — 体检,缺什么会明说(settings.json 缺 yds.* 键让用户补)。
|
|
27
|
+
2. `yds_build` — 从零构建 yms-home(首次下载 77MB kernel,约 19 分钟;之后约 1 分钟)+ 拉配置。
|
|
28
|
+
3. `yds_start` — 后台启动(socks 转发器/配置缺失自动补,幂等)。输出 rdbga cookie 值。
|
|
29
|
+
4. `yds_status` / `yds_logs` — 观察就绪(`/_yms/ready` → running;真实库首启 1–2 分钟)。
|
|
30
|
+
5. `yds_stop` 停止。
|
|
31
|
+
|
|
32
|
+
## 调试(JDWP 断点)
|
|
33
|
+
|
|
34
|
+
- `yds_ide apply` 写 ris-ide 程序配置前,若 `.dsh-ris-ide/config.json` 不存在,先按 ris-ide 的结构初始化(`version/defaults/programs`);条目名不叫 `yds-yms-*`,而是 **java=parent.artifactId、前端=package.json 平级 module.xml 的 module.name**(老名字会被识别并就地改名,不留重复行)。
|
|
35
|
+
- ide 条目必须 `kind: "spring"` 才能被 ris_dev_debug 识别(manual 条目会报「只有 Spring(Java) 程序支持调试」)——`yds_ide apply` 生成的条目已带;自定义 startCommand 优先于默认 spring 启动路径,调试走 `${JDWP_ARGS}` 占位符替换,行为不变。
|
|
36
|
+
|
|
37
|
+
`yds_ide {action:"apply"}` 把 `yds-yms-<module>` 条目写进 `.dsh-ris-ide/config.json`
|
|
38
|
+
(startCommand 含 `${JDWP_ARGS}` 占位符,仅 upsert 自己的条目,不碰用户其它条目)→
|
|
39
|
+
之后 `ris_dev_debug` 断点调试 / `ris_dev_start` / `ris_dev_stop` 走 dsh-ris-ide 的标准启停(日志进 logs/)。
|
|
40
|
+
|
|
41
|
+
**同时生成 `.vscode/launch.json`**(写在**项目根**——即含 `.vscode/settings.json` 的目录、
|
|
42
|
+
IDE 实际打开的文件夹,不是 DSH 会话 workspace 根;与 YonCode 的 `ensureLaunchConfig`
|
|
43
|
+
同名同构条目 `yms-middleware-cloudnative-jdk-21`):YonCode 端 F5 也能直接启动/调试 YMS 中间件,
|
|
44
|
+
两边写入互不冲突(同名条目、只 upsert 自己那条)。该条目的 vmArgs 含
|
|
45
|
+
`-Dapp.metainfo.start_mode=static`(**关键**:服务发现改读 `config/ip_node_mapping.json`
|
|
46
|
+
而不是 eureka——开发机到不了内部 k8s registry;实例不可达时 IrisInvoker 再回退
|
|
47
|
+
`domain.url` 云调试改写走公网 WAF)、`-Dyms_fetch_config_version=2.0.0`、
|
|
48
|
+
`-Dyms.bootstrap.enableNewConfig=true`、`-Dinotify_enable=false`,以及完整的
|
|
49
|
+
Java 21 add-exports/add-opens 集(少一条 add-opens 就会让 Liberty JNDI 初始化失败、
|
|
50
|
+
模块部署 fatal)。`yds start` 的 JVM 参数与之保持一致。
|
|
51
|
+
|
|
52
|
+
**构建日志可追踪**:ris_dev_start 的 startCommand 走 `yds start --foreground`——启动前
|
|
53
|
+
若 yms-home 未构建会自动跑 mvn,**mvn 全部输出与各阶段进度(`[yds] …` 行)流式写进
|
|
54
|
+
ris-ide 的日志文件**,用 `ris_dev_logs` / `yds_logs` 实时追踪(首次构建约 19 分钟、
|
|
55
|
+
kernel 缓存后约 1 分钟)。启动序列:mvn 构建 → confcenter 配置 → SOCKS 隧道预检 +
|
|
56
|
+
转发器 → Liberty 前台启动。
|
|
57
|
+
|
|
58
|
+
**SOCKS 隧道预检**:转发器启动前先做一次 SOCKS CONNECT 探测;隧道不可用(远程开发机
|
|
59
|
+
出口失效)时快速失败并给出明确提示,而不是等到模块部署 20 秒后报
|
|
60
|
+
`数据源无法访问数据库`。隧道是瞬时故障时稍后重试即可。
|
|
61
|
+
|
|
62
|
+
**内网端点全覆盖(yonbip 隧道化)**:SOCKS 模式下除了 DB/Redis,yonbip_config.json 里的
|
|
63
|
+
RFC1918 内网端点(kafka 6880 / ES 6888 / license 30001 等,端口≥1024)也自动改写成
|
|
64
|
+
`127.0.0.1:<同端口>` 并加入转发器目标。例外(均为平台侧限制,改写无意义):
|
|
65
|
+
`*.inner-yonbip-k8s.com` 域名(SOCKS 服务器解析不了 k8s 内部 DNS,保持原值+警告)、
|
|
66
|
+
端口<1024(本地无权限监听)。适配逻辑升级用 `.dsh-ris-yds/adapt-version` 戳标记,
|
|
67
|
+
版本不符时 `yds start` 自动从缓存重新适配(无需删文件)。
|
|
68
|
+
|
|
69
|
+
## API 调试
|
|
70
|
+
|
|
71
|
+
- 本地直连:`YYCToken` 头,`yds_token {url}` 生成(URL 绑定宽松,根 URL token 可打任意路径)。
|
|
72
|
+
- 浏览器路径:在 workbenchUrl 域下种 cookie `rdbga=<yds_start 输出的 base64>`,
|
|
73
|
+
请求 `/<module>/**` 会经平台网关路由回调到本地实例(会话上下文透传,网关会剥掉 `/<module>` 前缀)。
|
|
74
|
+
- **`rdbga` 必须唯一且非空**:YonCode 内置浏览器启动时可能写一个空值 host-only `rdbga`,
|
|
75
|
+
它的优先级高于带点域名的正确值 → 网关判定「非调试请求」,全转远端 pod:页面正常、本机
|
|
76
|
+
`logs/access.log` 空、断点不进,**故障完全静默**。先 `yds_browser {action:"status"}` 看(会打印期望值和来源),
|
|
77
|
+
再 `yds_browser {action:"fix"}`:fix **默认 dry_run=true 只预览**,确认计划后用 `dry_run:false` 真写
|
|
78
|
+
(删空值/重复/不匹配,写入期望值);CLI 对应 `yds browser fix [--rdbga=<值>] [--dry-run]`。
|
|
79
|
+
期望值默认按项目配置算(`module,/context@bind:port` 的 base64);运行中 JVM 的 `-Drdbga` 只用于校验,
|
|
80
|
+
所有运行实例的值都与配置不一致时**拒绝写入**并要求 `--rdbga` 指定(别把浏览器指向不存在的实例;
|
|
81
|
+
同机还有别的项目 JVM 时直接 `--rdbga` 最稳)。`yds start` 成功后会自动校准一次
|
|
82
|
+
(后台/前台/已在运行都会尝试,浏览器没开 DevTools 端口就跳过)。
|
|
83
|
+
- 业务接口 302 且日志里出现 `【yht_access_token_invalid】` / `YmsContextFilter [processOnUnLogin]`:
|
|
84
|
+
那是**会话**问题(不是代理/回调),控制器根本没执行;用已登录的工作台页签发请求,别用干净上下文。
|
|
85
|
+
- `POST /CloudRemoteCall/<接口全名>/<方法名>` 为 YMS RPC 形态。
|
|
86
|
+
|
|
87
|
+
## 代理(SOCKS)与端口归属
|
|
88
|
+
|
|
89
|
+
- `yds start` 前会做 SOCKS 预检(握手 + CONNECT 探测目标),隧道不可用会**快速失败**并给出原因,不会白等模块部署 2 分钟。
|
|
90
|
+
- 隧道优先借 dsh-ris-ide 的引擎(`yds_proxy sync|status`),`127.0.0.1:1521/6890/6880/6888/30001` 由它绑定;未装 ris-ide 才回退内置转发器——**不要手动再起第二套**,会抢端口。
|
|
91
|
+
- 引擎随 `dsh` 退出停止(宿主钩子),也可 `yds proxy stop` 手动停;配置与 pid 在 `~/.dsh-ris-ide/`。
|
|
92
|
+
- `yds proxy sync|start` 会把 SOCKS 参数写进 `.vscode/launch.json` 的 yds 条目(YonCode/VS Code F5 启动的 java 才走代理),`yds proxy stop` 自动摘掉——只动自己那条 configuration。
|
|
93
|
+
- 引擎与配置**全机一份、整机生效**:`~/.dsh-ris-ide/proxy.json`(`DSH_RIS_IDE_PROXY_HOME` 可改)。规则不分工作区/会话(`workspace` 只是来源标注);`yds_proxy sync` 只替换 `source=yds` 的行,用户自己的规则不受影响;yds 只写真实程序:`java`(内网 /24 整段、任意端口——JDBC/Redis 驱动不支持 SOCKS,只能靠系统层拦截)+ `git`(git.url 的主机)+ 带 `label` 的 `YonCode java`/`YonCode git`(`<YonCode>/devkit/*/bin/java.exe` 与系统 git,路径在项目外、项目配置推不出来,界面上显示 label)。端点不生成规则行(老的 `jdbc` 名字匹配不到任何进程);ProxyBridge 不可用时的 `127.0.0.1` 同端口兜底监听写在配置的 `shadows` 字段里,属于引擎输入、不进界面。
|
|
94
|
+
- **主路是 ProxyBridge**(系统层按进程拦截):`yds start` 发现隧道可用就自动启动它(提权走系统弹窗),`yds stop` 自动停止并清 git 代理;它没跑时注入与 127 映射自动当后备。手动入口 `yds proxy redirector [status|sync|start|stop]`。
|
|
95
|
+
- git 走 git 自身配置(`http.https://<host>/.proxy`),命令上不需要 `-c`;用 `yds proxy git-on|git-off` 控制。
|
|
96
|
+
- 相关工具:`yds_proxy`;相关 CLI:`yds proxy ...`。
|
|
97
|
+
- 浏览器 cookie 体检/校准:工具 `yds_browser`(fix 默认 dry-run),CLI `yds browser [status|fix] [--rdbga=<值>] [--dry-run]`;DevTools 端口清单的单一来源是 `lib/browser.js` 的 `DEVTOOLS_PORTS`(当前 9222/9223/9224/9333),不要另抄一份。
|
|
98
|
+
|
|
99
|
+
## 前端调试(本地 extend 脚本)
|
|
100
|
+
|
|
101
|
+
- 远端页面加载的是平台的 `extend*.min.js`;本地 webpack dev server(`npm run debug:extend`,默认 3004)
|
|
102
|
+
要接管它,三条路:
|
|
103
|
+
1. `yds fe rules` → 生成 `resource-override-rules.json`,让用户把它 Import 进 Chrome 的 Resource Override 扩展(`yds-chrome-resource-override.crx`)。
|
|
104
|
+
2. `yds fe plan` → 拿到 `{match, replace, regex, substitution}` 七条;**你自己就能用 CDP 注入**:
|
|
105
|
+
`Fetch.enable` 后对每个 `Fetch.requestPaused`,URL 命中 `regex` 就
|
|
106
|
+
`Fetch.continueRequest({ requestId, url: 用 → 右侧地址(`\1` 展开) })`,其余 `Fetch.continueRequest({ requestId })`。
|
|
107
|
+
3. `yds fe url <页面>` → 直接给带 `scriptUrl=http://127.0.0.1:<fe.port>/static` 的地址,零配置。
|
|
108
|
+
- 模块名/工作台主机/前端端口都来自项目配置(项目配置里的前端端口 可改),**不要硬编码 c-ass-kk-hans / srmdev 主机 / 3004**。
|
|
109
|
+
|
|
110
|
+
## YonCode 网卡 IP 校验
|
|
111
|
+
- `yds doctor` / `yds yoncode` 检查 YonCode 的 `serverBindAddress` 校验是否已降级为告警;`unpatched` 时**先问用户**(这是改别人的安装目录),同意后 `yds yoncode patch`,需要写 Program Files 就先说明要提权(管理员 / danger-full-access)。
|
|
112
|
+
- `yds yoncode revert` 从 `.bak` 还原;YonCode 升级重写文件后需重跑 patch。
|
|
113
|
+
|
|
114
|
+
## 硬约束/口径
|
|
115
|
+
|
|
116
|
+
- 项目源码只读;写只发生在 yms-home(YonCode 同构的标准部署布局)、`.dsh-ris-yds/`、`.dsh-ris-ide/config.json`(只 upsert 自己那一条)、项目根的 `.vscode/launch.json`(同样只 upsert 自己那条 `yms-middleware-cloudnative-jdk-21`,与 YonCode 生成物同名同构;其余 configuration 与注释/尾逗号原样保留——YonCode 自己每次启动也写这个文件)。唯一的浏览器侧写入是工作台域下的 `rdbga` cookie(只此一个名字、只在该域;失败静默,`--dry-run` 可先预览)。
|
|
117
|
+
- 进程身份校验:stop/状态只认 pidfile 里「经 /proc cmdline 验证是本工具进程」的 pid,pid 复用/重启后的陈旧 pidfile 自动清理,绝不误杀无关进程。
|
|
118
|
+
- 启动失败自动回滚:startDetached 端口 120 秒未就绪即报错并回收(TERM+pidfile 清理),SOCKS 转发器随之停止,不留孤儿进程。
|
|
119
|
+
- 前台模式(ris_dev_start)同样写 pidfile——`yds_status`/`yds_stop` 对 IDE 启动的实例可见;前台输出里也打印 rdbga cookie 行。
|
|
120
|
+
- `yds_logs` 只读日志尾部(≤256KB);yms-console.log 超过 16MB 自动轮转为 .old。
|
|
121
|
+
- CLI 未知命令/动作、多余参数/未知选项、执行失败都**非 0 退出**;agent 工具结果带 `ok` 字段(失败 `ok:false`),先看 `ok` 再解析文本。
|
|
122
|
+
- JDBC 适配支持 oracle @//service、oracle SID、mysql 三种形态;无法识别的 jdbc url 会打警告(不重写不转发)。
|
|
123
|
+
- 安全边界(用户已决策文档化):workbenchUrl 是项目级信任根(勿对不可信仓库跑 build/start);SOCKS 转发器本机无鉴权;`yds token` 明文输出(调试用途);Windows 停机为硬杀(无优雅 TERM);`yds browser fix` 会删/写用户浏览器的 `rdbga` cookie(仅工作台域、仅此名字、失败静默;不确定先 `--dry-run`)。
|
|
124
|
+
- 与 YonCode 共用工作区时:YonCode 的 sync 会把 yms-home/config 覆盖为远端原始地址(直连场景正好);
|
|
125
|
+
SOCKS 场景重跑 `yds_confcenter` 即可(幂等秒级)。
|
|
126
|
+
- git 只读(`git.commit=never`),clone 走 `git -c http.proxy=socks5h://<socks>`(仅当配置了 SOCKS)。
|
|
127
|
+
- 模块名从 yonbuilder.json/module.xml 自动识别,不要手工写死。
|
|
128
|
+
|
|
129
|
+
## ris-ide 只做外挂(监听,不改它)
|
|
130
|
+
|
|
131
|
+
- `yds ide apply` 注入 `id: yds:<项目名>` 的启动器行 + 按 `module.xml`/pom 修正 yds 项目的行名 + 禁用被启动器取代的后端行。
|
|
132
|
+
- 然后插件在宿主里监听 `.dsh-ris-ide/config.json`(`fs.watch` + 2s 轮询、400ms 去抖):ris-ide 初始化/刷新配置把条目冲掉时自动补回,其它行原样保留。登记在 `~/.dsh-ris-ys/ide-watch.json`…实际路径 `~/.dsh-ris-yds/ide-watch.json`。
|
|
133
|
+
- 不要为了联动去改 dsh-ris-ide(用户口径:ris-yds 不能影响 ris-ide 的使用)。
|
|
134
|
+
- `yds yoncode patch` 除了去掉网卡校验,还会给 YMS 的 vmArgs 注入 `-DsocksProxyHost/-DsocksProxyPort`(java 不再走 ProxyBridge:WinDivert 会把 Oracle TNS 握手 reset);`.env proxy.javaRules=on` 可恢复 java 代理规则。
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
RDBGA_NAME,
|
|
6
|
+
CdpError,
|
|
7
|
+
classifyRdbgaCookies,
|
|
8
|
+
connectCdp,
|
|
9
|
+
cookiesOfTargets,
|
|
10
|
+
describeRdbgaCookies,
|
|
11
|
+
domainMatches,
|
|
12
|
+
hostVariants,
|
|
13
|
+
isPlausibleRdbga,
|
|
14
|
+
probeDevtoolsPorts,
|
|
15
|
+
rdbgaFromCmdline,
|
|
16
|
+
rdbgaFromRunningJvm,
|
|
17
|
+
readRdbgaCookies,
|
|
18
|
+
repairRdbgaCookie,
|
|
19
|
+
workbenchTargets,
|
|
20
|
+
} from '../lib/browser.js'
|
|
21
|
+
import { rdbgaValue } from '../lib/yms.js'
|
|
22
|
+
|
|
23
|
+
// Generated by the same code that writes the cookie, not pasted: if the format ever
|
|
24
|
+
// changes, this fixture moves with it instead of drifting away from production.
|
|
25
|
+
const MODULE = 'c-ass-kk-hans'
|
|
26
|
+
const WORKBENCH_HOST = 'srmdev.hanslaser.com'
|
|
27
|
+
const EXPECTED = rdbgaValue({ module: MODULE, context: '/', serverBindAddress: '172.20.63.131', serverPort: 31010 })
|
|
28
|
+
const HOSTS = hostVariants(WORKBENCH_HOST)
|
|
29
|
+
|
|
30
|
+
/** Fake WebSocket: lets a test script the handshake and every command. */
|
|
31
|
+
function fakeSocket(script) {
|
|
32
|
+
return class FakeWebSocket {
|
|
33
|
+
constructor(url) {
|
|
34
|
+
this.url = url
|
|
35
|
+
this.sent = []
|
|
36
|
+
setTimeout(() => { this.onopen?.() }, 0)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
send(raw) {
|
|
40
|
+
const message = JSON.parse(raw)
|
|
41
|
+
this.sent.push(message)
|
|
42
|
+
const reply = script(message, this)
|
|
43
|
+
if (reply === null) return
|
|
44
|
+
setTimeout(() => { this.onmessage?.({ data: JSON.stringify({ id: message.id, ...reply }) }) }, 0)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
close() { this.closed = true; this.onclose?.() }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test('the rdbga value is read out of a JVM command line, and absent lines stay empty', () => {
|
|
52
|
+
assert.equal(rdbgaFromCmdline(`java -Drdbga=${EXPECTED} -Dserver.port=31010 -cp x.jar Main`), EXPECTED)
|
|
53
|
+
assert.equal(rdbgaFromCmdline('java -Dserver.port=31010 -cp x.jar Main'), '')
|
|
54
|
+
assert.equal(rdbgaFromCmdline(''), '')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('host variants cover the domain-cookie spelling, and domain matching is suffix-safe', () => {
|
|
58
|
+
assert.deepEqual(hostVariants('srmdev.hanslaser.com'), ['srmdev.hanslaser.com', '.srmdev.hanslaser.com'])
|
|
59
|
+
assert.deepEqual(hostVariants('.srmdev.hanslaser.com'), ['.srmdev.hanslaser.com', 'srmdev.hanslaser.com'])
|
|
60
|
+
assert.deepEqual(hostVariants(''), [])
|
|
61
|
+
assert.equal(domainMatches('.srmdev.hanslaser.com', HOSTS), true)
|
|
62
|
+
assert.equal(domainMatches('srmdev.hanslaser.com', HOSTS), true)
|
|
63
|
+
assert.equal(domainMatches('evil-srmdev.hanslaser.com', HOSTS), false)
|
|
64
|
+
assert.equal(domainMatches('other.com', HOSTS), false)
|
|
65
|
+
assert.equal(domainMatches('anything.com', []), true)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('a value is only written when it decodes to "module,/context@ipv4:port"', () => {
|
|
69
|
+
assert.equal(isPlausibleRdbga(EXPECTED), true)
|
|
70
|
+
assert.equal(isPlausibleRdbga(''), false)
|
|
71
|
+
assert.equal(isPlausibleRdbga('null,/@null:31010'), false) // not base64
|
|
72
|
+
assert.equal(isPlausibleRdbga(Buffer.from('null,/@null:31010').toString('base64')), false)
|
|
73
|
+
assert.equal(isPlausibleRdbga(Buffer.from('c-ass-kk-hans,/@172.20.63.131:31010').toString('base64')), true)
|
|
74
|
+
assert.equal(isPlausibleRdbga('not base64 !!'), false)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('an empty host-only rdbga is removed: it shadows the correct value', () => {
|
|
78
|
+
const verdict = classifyRdbgaCookies([
|
|
79
|
+
{ name: RDBGA_NAME, value: '', domain: 'srmdev.hanslaser.com', path: '/' },
|
|
80
|
+
{ name: RDBGA_NAME, value: EXPECTED, domain: '.srmdev.hanslaser.com', path: '/' },
|
|
81
|
+
], EXPECTED)
|
|
82
|
+
assert.equal(verdict.keep.length, 1)
|
|
83
|
+
assert.equal(verdict.keep[0].domain, '.srmdev.hanslaser.com')
|
|
84
|
+
assert.equal(verdict.remove.length, 1)
|
|
85
|
+
assert.equal(verdict.remove[0].value, '')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('only one rdbga survives: duplicates are removed even when the value matches', () => {
|
|
89
|
+
const verdict = classifyRdbgaCookies([
|
|
90
|
+
{ name: RDBGA_NAME, value: EXPECTED, domain: '.srmdev.hanslaser.com', path: '/' },
|
|
91
|
+
{ name: RDBGA_NAME, value: EXPECTED, domain: 'srmdev.hanslaser.com', path: '/' },
|
|
92
|
+
{ name: 'JSESSIONID', value: 'abc', domain: '.hanslaser.com', path: '/' },
|
|
93
|
+
], EXPECTED)
|
|
94
|
+
assert.equal(verdict.keep.length, 1)
|
|
95
|
+
assert.equal(verdict.remove.length, 1)
|
|
96
|
+
assert.ok(verdict.remove.every((cookie) => cookie.name === RDBGA_NAME))
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('a stale value is removed as well, and another domain is left alone', () => {
|
|
100
|
+
const verdict = classifyRdbgaCookies([
|
|
101
|
+
{ name: RDBGA_NAME, value: 'b2xkLXN0YWxl', domain: 'srmdev.hanslaser.com', path: '/' },
|
|
102
|
+
{ name: RDBGA_NAME, value: 'x', domain: 'elsewhere.com', path: '/' },
|
|
103
|
+
], EXPECTED, HOSTS)
|
|
104
|
+
assert.equal(verdict.keep.length, 0)
|
|
105
|
+
assert.equal(verdict.remove.length, 1)
|
|
106
|
+
assert.equal(verdict.remove[0].domain, 'srmdev.hanslaser.com')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('the devtools probe takes the first port that answers', async () => {
|
|
110
|
+
const seen = []
|
|
111
|
+
const fetchImpl = async (url) => {
|
|
112
|
+
seen.push(url)
|
|
113
|
+
if (url.includes(':9222/')) throw new Error('ECONNREFUSED')
|
|
114
|
+
return { ok: true, json: async () => ({ Browser: 'Chrome/151' }) }
|
|
115
|
+
}
|
|
116
|
+
assert.deepEqual(await probeDevtoolsPorts([9222, 9223], { fetchImpl }), { port: 9223, browser: 'Chrome/151' })
|
|
117
|
+
assert.equal(seen.length, 2)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('no port answering is reported as port 0, not as an error', async () => {
|
|
121
|
+
const fetchImpl = async () => { throw new Error('ECONNREFUSED') }
|
|
122
|
+
assert.deepEqual(await probeDevtoolsPorts([9222], { fetchImpl }), { port: 0, browser: '' })
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('a hanging DevTools endpoint is cut off by the deadline', async () => {
|
|
126
|
+
const fetchImpl = () => new Promise(() => {})
|
|
127
|
+
const started = Date.now()
|
|
128
|
+
const found = await probeDevtoolsPorts([9222], { fetchImpl, timeout: 50 })
|
|
129
|
+
assert.deepEqual(found, { port: 0, browser: '' })
|
|
130
|
+
assert.ok(Date.now() - started < 1000)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('only workbench pages are treated as targets, and a broken listing is reported', async () => {
|
|
134
|
+
const fetchImpl = async () => ({
|
|
135
|
+
ok: true,
|
|
136
|
+
json: async () => ([
|
|
137
|
+
{ type: 'page', url: 'https://srmdev.hanslaser.com/', webSocketDebuggerUrl: 'ws://a' },
|
|
138
|
+
{ type: 'page', url: 'https://example.com/', webSocketDebuggerUrl: 'ws://b' },
|
|
139
|
+
{ type: 'service_worker', url: 'https://srmdev.hanslaser.com/sw.js', webSocketDebuggerUrl: 'ws://c' },
|
|
140
|
+
]),
|
|
141
|
+
})
|
|
142
|
+
const listed = await workbenchTargets(9222, { fetchImpl, hosts: HOSTS })
|
|
143
|
+
assert.equal(listed.ok, true)
|
|
144
|
+
assert.equal(listed.targets.length, 1)
|
|
145
|
+
assert.equal(listed.targets[0].webSocketDebuggerUrl, 'ws://a')
|
|
146
|
+
const broken = await workbenchTargets(9222, { fetchImpl: async () => { throw new Error('down') } })
|
|
147
|
+
assert.equal(broken.ok, false)
|
|
148
|
+
assert.match(broken.error, /页签/)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('repair deletes the empty cookie and sets the expected one', async () => {
|
|
152
|
+
const calls = []
|
|
153
|
+
const cookies = [
|
|
154
|
+
{ name: RDBGA_NAME, value: '', domain: 'srmdev.hanslaser.com', path: '/' },
|
|
155
|
+
{ name: 'JSESSIONID', value: 'abc', domain: '.hanslaser.com', path: '/' },
|
|
156
|
+
]
|
|
157
|
+
const fetchImpl = async () => ({
|
|
158
|
+
ok: true,
|
|
159
|
+
json: async () => ([{ type: 'page', url: 'https://srmdev.hanslaser.com/home', webSocketDebuggerUrl: 'ws://a' }]),
|
|
160
|
+
})
|
|
161
|
+
const connect = async () => ({
|
|
162
|
+
send: async (method, params) => {
|
|
163
|
+
calls.push([method, params])
|
|
164
|
+
if (method === 'Network.getCookies') return { cookies }
|
|
165
|
+
return {}
|
|
166
|
+
},
|
|
167
|
+
close: () => { calls.push(['close']) },
|
|
168
|
+
})
|
|
169
|
+
const result = await repairRdbgaCookie({ port: 9222, expected: EXPECTED, fetchImpl, connect })
|
|
170
|
+
assert.deepEqual(
|
|
171
|
+
{ ok: result.ok, tabs: result.tabs, removed: result.removed, wrote: result.wrote, dryRun: result.dryRun },
|
|
172
|
+
{ ok: true, tabs: 1, removed: 1, wrote: 1, dryRun: false },
|
|
173
|
+
)
|
|
174
|
+
assert.equal(calls[0][0], 'Network.enable')
|
|
175
|
+
const deletes = calls.filter(([method]) => method === 'Network.deleteCookies')
|
|
176
|
+
assert.equal(deletes.length, 1)
|
|
177
|
+
assert.equal(deletes[0][1].domain, 'srmdev.hanslaser.com')
|
|
178
|
+
const sets = calls.filter(([method]) => method === 'Network.setCookie')
|
|
179
|
+
assert.equal(sets.length, 1)
|
|
180
|
+
assert.equal(sets[0][1].value, EXPECTED)
|
|
181
|
+
assert.equal(sets[0][1].url, 'https://srmdev.hanslaser.com/')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('repair deletes with the cookie own domain/path, not the tab host', async () => {
|
|
185
|
+
const calls = []
|
|
186
|
+
const fetchImpl = async () => ({
|
|
187
|
+
ok: true,
|
|
188
|
+
json: async () => ([{ type: 'page', url: 'https://srmdev.hanslaser.com/', webSocketDebuggerUrl: 'ws://a' }]),
|
|
189
|
+
})
|
|
190
|
+
const connect = async () => ({
|
|
191
|
+
send: async (method, params) => {
|
|
192
|
+
calls.push([method, params])
|
|
193
|
+
if (method === 'Network.getCookies') {
|
|
194
|
+
return { cookies: [{ name: RDBGA_NAME, value: '', domain: '.srmdev.hanslaser.com', path: '/foo' }] }
|
|
195
|
+
}
|
|
196
|
+
return {}
|
|
197
|
+
},
|
|
198
|
+
close: () => {},
|
|
199
|
+
})
|
|
200
|
+
const result = await repairRdbgaCookie({ port: 9222, expected: EXPECTED, fetchImpl, connect })
|
|
201
|
+
assert.equal(result.ok, true)
|
|
202
|
+
const [method, params] = calls.find(([name]) => name === 'Network.deleteCookies')
|
|
203
|
+
assert.equal(method, 'Network.deleteCookies')
|
|
204
|
+
assert.equal(params.domain, '.srmdev.hanslaser.com')
|
|
205
|
+
assert.equal(params.path, '/foo')
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test('a tab that cannot be reached is a reported failure, not a silent success', async () => {
|
|
209
|
+
const fetchImpl = async () => ({
|
|
210
|
+
ok: true,
|
|
211
|
+
json: async () => ([
|
|
212
|
+
{ type: 'page', url: 'https://srmdev.hanslaser.com/a', webSocketDebuggerUrl: 'ws://a' },
|
|
213
|
+
{ type: 'page', url: 'https://srmdev.hanslaser.com/b', webSocketDebuggerUrl: 'ws://b' },
|
|
214
|
+
]),
|
|
215
|
+
})
|
|
216
|
+
const connect = async (wsUrl) => {
|
|
217
|
+
if (wsUrl === 'ws://b') throw new Error('connection closed')
|
|
218
|
+
return { send: async () => ({ cookies: [] }), close: () => {} }
|
|
219
|
+
}
|
|
220
|
+
const result = await repairRdbgaCookie({ port: 9222, expected: EXPECTED, fetchImpl, connect })
|
|
221
|
+
assert.equal(result.ok, false)
|
|
222
|
+
assert.equal(result.tabs, 1)
|
|
223
|
+
assert.equal(result.failed.length, 1)
|
|
224
|
+
assert.match(result.error, /部分页签失败(成功 1 个,失败 1 个)/)
|
|
225
|
+
assert.match(result.error, /srmdev\.hanslaser\.com\/b: connection closed/)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('classify and describe agree when the cookie value is not a string', () => {
|
|
229
|
+
const cookies = [{ name: RDBGA_NAME, value: '123', domain: `srmdev.hanslaser.com`, path: '/' }]
|
|
230
|
+
const verdict = classifyRdbgaCookies(cookies, 123, HOSTS)
|
|
231
|
+
assert.equal(verdict.keep.length, 1)
|
|
232
|
+
assert.equal(verdict.remove.length, 0)
|
|
233
|
+
assert.match(describeRdbgaCookies(cookies, 123, HOSTS), /→ 正确/)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test('repair leaves a correct cookie alone and never sets a duplicate', async () => {
|
|
237
|
+
const calls = []
|
|
238
|
+
const fetchImpl = async () => ({
|
|
239
|
+
ok: true,
|
|
240
|
+
json: async () => ([{ type: 'page', url: 'https://srmdev.hanslaser.com/', webSocketDebuggerUrl: 'ws://a' }]),
|
|
241
|
+
})
|
|
242
|
+
const connect = async () => ({
|
|
243
|
+
send: async (method, params) => {
|
|
244
|
+
calls.push([method, params])
|
|
245
|
+
if (method === 'Network.getCookies') {
|
|
246
|
+
return { cookies: [{ name: RDBGA_NAME, value: EXPECTED, domain: '.srmdev.hanslaser.com', path: '/' }] }
|
|
247
|
+
}
|
|
248
|
+
return {}
|
|
249
|
+
},
|
|
250
|
+
close: () => {},
|
|
251
|
+
})
|
|
252
|
+
const result = await repairRdbgaCookie({ port: 9222, expected: EXPECTED, fetchImpl, connect })
|
|
253
|
+
assert.equal(result.removed, 0)
|
|
254
|
+
assert.equal(result.wrote, 0)
|
|
255
|
+
assert.equal(calls.filter(([method]) => method === 'Network.setCookie').length, 0)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test('repair refuses to write a value it cannot verify', async () => {
|
|
259
|
+
const calls = []
|
|
260
|
+
const result = await repairRdbgaCookie({
|
|
261
|
+
port: 9222,
|
|
262
|
+
expected: Buffer.from('null,/@null:31010').toString('base64'),
|
|
263
|
+
fetchImpl: async () => { throw new Error('must not be called') },
|
|
264
|
+
connect: async () => ({ send: async () => { calls.push('send') }, close: () => {} }),
|
|
265
|
+
})
|
|
266
|
+
assert.equal(result.ok, false)
|
|
267
|
+
assert.match(result.error, /拒绝写入/)
|
|
268
|
+
assert.equal(calls.length, 0)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('no workbench tab is a reported miss, not a crash', async () => {
|
|
272
|
+
const fetchImpl = async () => ({ ok: true, json: async () => ([]) })
|
|
273
|
+
const result = await repairRdbgaCookie({ port: 9222, expected: EXPECTED, fetchImpl, connect: async () => { throw new Error('never') } })
|
|
274
|
+
assert.equal(result.ok, false)
|
|
275
|
+
assert.match(result.error, /工作台/)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test('a CDP error rejects the command instead of looking like success', async () => {
|
|
279
|
+
const FakeWebSocket = fakeSocket((message) => ({ error: { code: -32000, message: `no way: ${message.method}` } }))
|
|
280
|
+
const cdp = await connectCdp('ws://fake', { WebSocketImpl: FakeWebSocket, timeout: 500 })
|
|
281
|
+
await assert.rejects(() => cdp.send('Network.setCookie', {}), (error) => error instanceof CdpError && /no way/.test(error.message))
|
|
282
|
+
cdp.close()
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test('a browser that stops answering cannot hang the caller', async () => {
|
|
286
|
+
const FakeWebSocket = fakeSocket(() => null) // accept every command, answer none
|
|
287
|
+
const cdp = await connectCdp('ws://fake', { WebSocketImpl: FakeWebSocket, timeout: 60 })
|
|
288
|
+
const started = Date.now()
|
|
289
|
+
await assert.rejects(() => cdp.send('Network.getCookies'), /超时/)
|
|
290
|
+
assert.ok(Date.now() - started < 1000)
|
|
291
|
+
cdp.close()
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
test('closing the socket fails the commands still in flight', async () => {
|
|
295
|
+
// Whatever the browser was doing, a socket that goes away must settle the command
|
|
296
|
+
// right then — waiting for the deadline would report a misleading timeout.
|
|
297
|
+
const FakeWebSocket = fakeSocket((message, socket) => {
|
|
298
|
+
setTimeout(() => { socket.onclose?.() }, 10)
|
|
299
|
+
return null
|
|
300
|
+
})
|
|
301
|
+
const cdp = await connectCdp('ws://fake', { WebSocketImpl: FakeWebSocket, timeout: 5000 })
|
|
302
|
+
await assert.rejects(() => cdp.send('Network.getCookies'), /已关闭/)
|
|
303
|
+
cdp.close()
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
test('cookies are read for every tab, without a URL filter', async () => {
|
|
307
|
+
const seen = []
|
|
308
|
+
const connect = async () => ({
|
|
309
|
+
send: async (method, params) => { seen.push([method, params]); return { cookies: [{ name: RDBGA_NAME, value: EXPECTED, domain: '.srmdev.hanslaser.com', path: '/' }] } },
|
|
310
|
+
close: () => {},
|
|
311
|
+
})
|
|
312
|
+
const cookies = await cookiesOfTargets([{ url: 'https://a/', webSocketDebuggerUrl: 'ws://a' }, { url: 'https://b/', webSocketDebuggerUrl: 'ws://b' }], { connect })
|
|
313
|
+
assert.equal(cookies.length, 2)
|
|
314
|
+
const gets = seen.filter(([method]) => method === 'Network.getCookies')
|
|
315
|
+
assert.equal(gets.length, 2)
|
|
316
|
+
assert.equal(gets[0][1], undefined) // no `urls`: a path-scoped rdbga stays visible
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
test('read reports the tab listing error instead of an empty cookie list', async () => {
|
|
320
|
+
const read = await readRdbgaCookies({ port: 9222, fetchImpl: async () => { throw new Error('down') } })
|
|
321
|
+
assert.equal(read.ok, false)
|
|
322
|
+
assert.match(read.error, /页签/)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
test('status text names the empty cookie as the problem', () => {
|
|
326
|
+
const text = describeRdbgaCookies([
|
|
327
|
+
{ name: RDBGA_NAME, value: '', domain: 'srmdev.hanslaser.com', path: '/' },
|
|
328
|
+
{ name: RDBGA_NAME, value: EXPECTED, domain: '.srmdev.hanslaser.com', path: '/' },
|
|
329
|
+
], EXPECTED)
|
|
330
|
+
assert.match(text, /空值/)
|
|
331
|
+
assert.match(text, /正确/)
|
|
332
|
+
assert.match(text, /需要清理 1 个/)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
test('workbench targets are matched by exact host, not by substring', async () => {
|
|
336
|
+
// A URL that merely *contains* the workbench host (trailing ".evil.com" or a query
|
|
337
|
+
// string) is not the workbench: writing there would silently target another site.
|
|
338
|
+
const fetchImpl = async () => ({
|
|
339
|
+
ok: true,
|
|
340
|
+
json: async () => ([
|
|
341
|
+
{ type: 'page', url: 'https://srmdev.hanslaser.com/', webSocketDebuggerUrl: 'ws://ok' },
|
|
342
|
+
{ type: 'page', url: 'https://SRMDEV.Hanslaser.com/home', webSocketDebuggerUrl: 'ws://ok2' },
|
|
343
|
+
{ type: 'page', url: 'https://srmdev.hanslaser.com.evil.com/', webSocketDebuggerUrl: 'ws://evil' },
|
|
344
|
+
{ type: 'page', url: 'https://evil.com/?next=https://srmdev.hanslaser.com/', webSocketDebuggerUrl: 'ws://query' },
|
|
345
|
+
{ type: 'page', url: 'https://sub.srmdev.hanslaser.com/', webSocketDebuggerUrl: 'ws://sub' },
|
|
346
|
+
{ type: 'page', url: 'https://srmdev.hanslaser.com/alive', webSocketDebuggerUrl: null },
|
|
347
|
+
{ type: 'page', url: 'https://srmdev.hanslaser.com/no-url' },
|
|
348
|
+
]),
|
|
349
|
+
})
|
|
350
|
+
const listed = await workbenchTargets(9222, { fetchImpl, hosts: HOSTS })
|
|
351
|
+
assert.equal(listed.ok, true)
|
|
352
|
+
assert.deepEqual(listed.targets.map((target) => target.webSocketDebuggerUrl), ['ws://ok', 'ws://ok2'])
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
test('a caller-provided target list is reused instead of listing the tabs again', async () => {
|
|
356
|
+
const fetched = []
|
|
357
|
+
const fetchImpl = async (url) => { fetched.push(url); throw new Error('must not be called') }
|
|
358
|
+
const targets = [{ url: 'https://srmdev.hanslaser.com/', webSocketDebuggerUrl: 'ws://a' }]
|
|
359
|
+
const cookies = [{ name: RDBGA_NAME, value: EXPECTED, domain: `.${WORKBENCH_HOST}`, path: '/' }]
|
|
360
|
+
const connect = async () => ({
|
|
361
|
+
send: async (method) => (method === 'Network.getCookies' ? { cookies } : {}),
|
|
362
|
+
close: () => {},
|
|
363
|
+
})
|
|
364
|
+
const read = await readRdbgaCookies({ port: 9222, targets, fetchImpl, connect, hosts: HOSTS })
|
|
365
|
+
assert.deepEqual({ ok: read.ok, cookies: read.cookies.length }, { ok: true, cookies: 1 })
|
|
366
|
+
const repaired = await repairRdbgaCookie({ port: 9222, targets, expected: EXPECTED, fetchImpl, connect, hosts: HOSTS })
|
|
367
|
+
assert.deepEqual({ ok: repaired.ok, wrote: repaired.wrote }, { ok: true, wrote: 0 })
|
|
368
|
+
assert.equal(fetched.length, 0)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
test('the JVM query always resolves to a report instead of throwing', async () => {
|
|
372
|
+
const live = await rdbgaFromRunningJvm()
|
|
373
|
+
assert.equal(typeof live.ok, 'boolean')
|
|
374
|
+
assert.ok(Array.isArray(live.values))
|
|
375
|
+
if (live.ok === false) assert.match(live.error, /JVM|java/)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
test('dry-run reports the plan without deleting or writing any cookie', async () => {
|
|
379
|
+
const calls = []
|
|
380
|
+
const targets = [{ url: `https://${WORKBENCH_HOST}/`, webSocketDebuggerUrl: 'ws://a' }]
|
|
381
|
+
const connectFor = (cookies) => async () => ({
|
|
382
|
+
send: async (method) => { calls.push(method); return method === 'Network.getCookies' ? { cookies } : {} },
|
|
383
|
+
close: () => {},
|
|
384
|
+
})
|
|
385
|
+
const emptyAndCorrect = [
|
|
386
|
+
{ name: RDBGA_NAME, value: '', domain: WORKBENCH_HOST, path: '/' },
|
|
387
|
+
{ name: RDBGA_NAME, value: EXPECTED, domain: `.${WORKBENCH_HOST}`, path: '/' },
|
|
388
|
+
]
|
|
389
|
+
const plan = await repairRdbgaCookie({
|
|
390
|
+
port: 9222, targets, expected: EXPECTED, connect: connectFor(emptyAndCorrect), dryRun: true,
|
|
391
|
+
})
|
|
392
|
+
assert.deepEqual(
|
|
393
|
+
{ ok: plan.ok, tabs: plan.tabs, removed: plan.removed, wrote: plan.wrote, dryRun: plan.dryRun },
|
|
394
|
+
{ ok: true, tabs: 1, removed: 1, wrote: 0, dryRun: true },
|
|
395
|
+
)
|
|
396
|
+
assert.ok(!calls.includes('Network.deleteCookies'))
|
|
397
|
+
assert.ok(!calls.includes('Network.setCookie'))
|
|
398
|
+
|
|
399
|
+
const onlyEmpty = await repairRdbgaCookie({
|
|
400
|
+
port: 9222,
|
|
401
|
+
targets,
|
|
402
|
+
expected: EXPECTED,
|
|
403
|
+
connect: connectFor([{ name: RDBGA_NAME, value: '', domain: WORKBENCH_HOST, path: '/' }]),
|
|
404
|
+
dryRun: true,
|
|
405
|
+
})
|
|
406
|
+
assert.deepEqual({ removed: onlyEmpty.removed, wrote: onlyEmpty.wrote }, { removed: 1, wrote: 1 })
|
|
407
|
+
assert.ok(!calls.includes('Network.deleteCookies'))
|
|
408
|
+
assert.ok(!calls.includes('Network.setCookie'))
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
test('status compares as strings and tells same-prefix cookies apart', () => {
|
|
412
|
+
// The old text compared with === (a numeric cookie would look wrong) and only showed
|
|
413
|
+
// the first 12 characters, so two values sharing the prefix looked identical.
|
|
414
|
+
const near = Buffer.from(`${MODULE},/@172.20.63.131:31011`).toString('base64')
|
|
415
|
+
assert.notEqual(EXPECTED.slice(-6), near.slice(-6))
|
|
416
|
+
const text = describeRdbgaCookies([
|
|
417
|
+
{ name: RDBGA_NAME, value: near, domain: `.${WORKBENCH_HOST}`, path: '/' },
|
|
418
|
+
{ name: RDBGA_NAME, value: EXPECTED, domain: `.${WORKBENCH_HOST}`, path: '/' },
|
|
419
|
+
], EXPECTED)
|
|
420
|
+
assert.match(text, /1\. /)
|
|
421
|
+
assert.match(text, /2\. /)
|
|
422
|
+
assert.match(text, /值不匹配/)
|
|
423
|
+
assert.ok(text.includes(near.slice(-6)))
|
|
424
|
+
assert.ok(text.includes(EXPECTED.slice(-6)))
|
|
425
|
+
assert.match(text, /需要清理 1 个/)
|
|
426
|
+
|
|
427
|
+
const numeric = describeRdbgaCookies(
|
|
428
|
+
[{ name: RDBGA_NAME, value: '123', domain: `.${WORKBENCH_HOST}`, path: '/' }],
|
|
429
|
+
123,
|
|
430
|
+
)
|
|
431
|
+
assert.match(numeric, /正确/)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
test('an unknown expectation is reported as unknown instead of "all wrong"', () => {
|
|
435
|
+
const text = describeRdbgaCookies(
|
|
436
|
+
[{ name: RDBGA_NAME, value: EXPECTED, domain: `.${WORKBENCH_HOST}`, path: '/' }],
|
|
437
|
+
'',
|
|
438
|
+
)
|
|
439
|
+
assert.match(text, /无法确定/)
|
|
440
|
+
assert.match(text, /值未知/)
|
|
441
|
+
assert.doesNotMatch(text, /需要清理/)
|
|
442
|
+
})
|