fix: 修复NapCat已登录误判旧二维码复用
This commit is contained in:
parent
0497233798
commit
3647ec9a7c
@ -12,6 +12,8 @@ RUN set -eux; \
|
||||
fonts-wqy-microhei \
|
||||
locales \
|
||||
tzdata \
|
||||
unzip \
|
||||
zip \
|
||||
xdg-utils; \
|
||||
sed -i 's/^# *zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen; \
|
||||
locale-gen zh_CN.UTF-8; \
|
||||
@ -20,6 +22,18 @@ RUN set -eux; \
|
||||
fc-cache -fv; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY ci/napcat-desktop-cn/patches/qq-login-real-online-guard.sh /tmp/qq-login-real-online-guard.sh
|
||||
RUN set -eux; \
|
||||
sed -i 's/\r$//' /tmp/qq-login-real-online-guard.sh; \
|
||||
rm -rf /tmp/NapCat.Shell; \
|
||||
unzip -q /app/NapCat.Shell.zip -d /tmp/NapCat.Shell; \
|
||||
NAPCAT_PATCH_ROOT=/tmp/NapCat.Shell sh /tmp/qq-login-real-online-guard.sh; \
|
||||
cd /tmp/NapCat.Shell; \
|
||||
zip -qr /app/NapCat.Shell.zip .; \
|
||||
rm -rf /tmp/NapCat.Shell /tmp/qq-login-real-online-guard.sh
|
||||
COPY ci/napcat-desktop-cn/verify.sh /ci/napcat-desktop-cn/verify.sh
|
||||
RUN sed -i 's/\r$//' /ci/napcat-desktop-cn/verify.sh && chmod +x /ci/napcat-desktop-cn/verify.sh
|
||||
|
||||
ENV LANG=zh_CN.UTF-8 \
|
||||
LC_ALL=zh_CN.UTF-8 \
|
||||
LANGUAGE=zh_CN:zh \
|
||||
|
||||
@ -7,14 +7,18 @@ $baseImage = docker image inspect mlikiowa/napcat-docker:latest --format '{{inde
|
||||
if (-not $baseImage) { throw 'NapCat upstream image digest not found; pull and inspect the image before building.' }
|
||||
docker build `
|
||||
--build-arg NAPCAT_BASE_IMAGE=$baseImage `
|
||||
-t kt-napcat-desktop-cn:desktop-cn-v1 `
|
||||
-t kt-napcat-desktop-cn:desktop-cn-v2 `
|
||||
-f ci/napcat-desktop-cn/Dockerfile .
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```powershell
|
||||
docker run --rm kt-napcat-desktop-cn:desktop-cn-v1 sh /ci/napcat-desktop-cn/verify.sh
|
||||
```bash
|
||||
name="kt-napcat-verify-$(date +%s)"
|
||||
trap 'docker rm -f "$name" >/dev/null 2>&1 || true' EXIT
|
||||
docker run -d --name "$name" kt-napcat-desktop-cn:desktop-cn-v2 >/dev/null
|
||||
sleep 3
|
||||
docker exec "$name" sh /ci/napcat-desktop-cn/verify.sh
|
||||
```
|
||||
|
||||
Record the final digest in `QQBOT_NAPCAT_IMAGE`.
|
||||
|
||||
65
ci/napcat-desktop-cn/patches/qq-login-real-online-guard.sh
Normal file
65
ci/napcat-desktop-cn/patches/qq-login-real-online-guard.sh
Normal file
@ -0,0 +1,65 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
ROOT="${NAPCAT_PATCH_ROOT:-/app/napcat}"
|
||||
PATCHED_LIST="$(mktemp)"
|
||||
PERL_PATCH="$(mktemp)"
|
||||
trap 'rm -f "$PATCHED_LIST" "$PERL_PATCH"' EXIT
|
||||
|
||||
cat > "$PERL_PATCH" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my ($file) = @ARGV;
|
||||
open my $in, '<', $file or die "read $file failed: $!";
|
||||
local $/;
|
||||
my $source = <$in>;
|
||||
close $in;
|
||||
|
||||
exit 0 if index($source, 'QQ Is Logined') < 0;
|
||||
exit 0 if index($source, 'RefreshQRcode') < 0;
|
||||
exit 0 if index($source, 'getQQLoginStatus') < 0;
|
||||
|
||||
my ($runtime) = $source =~ /([A-Za-z_\$][\w\$]*)\.getOneBotContext\(\)\?\.core\?\.selfInfo\?\.online,\s*[A-Za-z_\$][\w\$]*\s*=\s*\1\.getQQLoginStatus\(\)/;
|
||||
die "Unable to identify NapCat WebUI runtime guard symbols in $file\n" unless $runtime;
|
||||
|
||||
my ($send_error) = $source =~ /return\s+([A-Za-z_\$][\w\$]*)\(e,\s*"QQ Is Logined"\)/;
|
||||
die "Unable to identify NapCat sendError helper in $file\n" unless $send_error;
|
||||
|
||||
my $count = 0;
|
||||
$count += $source =~ s/if \(\Q$runtime\E\.getQQLoginStatus\(\)\)\n(\s*)return \Q$send_error\E\(e, "QQ Is Logined"\);/
|
||||
"if ($runtime.getQQLoginStatus() && $runtime.getOneBotContext()?.core?.selfInfo?.online !== false)\n"
|
||||
. "$1return $send_error(e, \"QQ Is Logined\");\n"
|
||||
. " if ($runtime.getQQLoginStatus() && $runtime.getOneBotContext()?.core?.selfInfo?.online === false)\n"
|
||||
. "$1$runtime.setQQLoginStatus(false);"
|
||||
/ge;
|
||||
|
||||
my $refresh_pattern = qr/\Q$runtime\E\.getQQLoginStatus\(\) \? \Q$send_error\E\(e, "QQ Is Logined"\) : \(await \Q$runtime\E\.refreshQRCode\(\),/;
|
||||
my $refresh_replacement =
|
||||
"$runtime.getQQLoginStatus() && $runtime.getOneBotContext()?.core?.selfInfo?.online !== false "
|
||||
. "? $send_error(e, \"QQ Is Logined\") "
|
||||
. ": ($runtime.getQQLoginStatus() && $runtime.getOneBotContext()?.core?.selfInfo?.online === false && $runtime.setQQLoginStatus(false), await $runtime.refreshQRCode(),";
|
||||
$count += $source =~ s/$refresh_pattern/$refresh_replacement/g;
|
||||
|
||||
exit 0 if $count == 0;
|
||||
open my $out, '>', $file or die "write $file failed: $!";
|
||||
print {$out} $source;
|
||||
close $out;
|
||||
print "$file\n";
|
||||
PERL
|
||||
|
||||
find "$ROOT" \
|
||||
-type f \( -name '*.js' -o -name '*.mjs' \) \
|
||||
! -path '*/node_modules/*' \
|
||||
! -path '*/static/*' \
|
||||
-size -20M \
|
||||
-print | while IFS= read -r file; do
|
||||
perl "$PERL_PATCH" "$file" >> "$PATCHED_LIST"
|
||||
done
|
||||
|
||||
if [ ! -s "$PATCHED_LIST" ]; then
|
||||
echo 'No NapCat WebUI login guard file was patched' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'Patched NapCat WebUI real-online login guard in %s file(s).\n' "$(wc -l < "$PATCHED_LIST" | tr -d ' ')"
|
||||
@ -10,3 +10,5 @@ test "XDG_CACHE_HOME=${XDG_CACHE_HOME:-}" = "XDG_CACHE_HOME=/app/.cache"
|
||||
test "XDG_DATA_HOME=${XDG_DATA_HOME:-}" = "XDG_DATA_HOME=/app/.local/share"
|
||||
test ! -e /.dockerenv
|
||||
grep -q '^0::/$' /proc/1/cgroup
|
||||
unzip -p /app/NapCat.Shell.zip napcat.mjs | grep -q 'selfInfo?.online !== false'
|
||||
unzip -p /app/NapCat.Shell.zip napcat.mjs | grep -q 'setQQLoginStatus(false)'
|
||||
|
||||
@ -33,7 +33,7 @@ spec:
|
||||
- name: DB_TIMEZONE
|
||||
value: "+08:00"
|
||||
- name: QQBOT_NAPCAT_IMAGE
|
||||
value: kt-napcat-desktop-cn:desktop-cn-v1
|
||||
value: kt-napcat-desktop-cn:desktop-cn-v2
|
||||
- name: QQBOT_NAPCAT_SSH_KEY_PATH
|
||||
value: /app/secrets/napcat-ssh/id_rsa
|
||||
- name: QQBOT_PLUGIN_TASK_QUEUE_REDIS_PREFIX
|
||||
|
||||
@ -596,6 +596,7 @@ export class ToolsService {
|
||||
'NapCat 请求超时',
|
||||
'NapCat 未返回登录二维码',
|
||||
'NapCat 二维码仍未刷新',
|
||||
'NapCat WebUI 登录态仍阻止生成新二维码',
|
||||
'QRCode Get Error',
|
||||
'socket hang up',
|
||||
]);
|
||||
|
||||
@ -2110,6 +2110,9 @@ export class QqbotNapcatLoginService {
|
||||
return this.toolsService.ensureFreshQrcode(qrcode, options);
|
||||
} catch (err) {
|
||||
if (this.toolsService.isNapcatAlreadyLoggedInError(err)) {
|
||||
if (options.requireFresh) {
|
||||
throw new Error('NapCat WebUI 登录态仍阻止生成新二维码');
|
||||
}
|
||||
const status = await this.getLoginStatus(container);
|
||||
return this.toolsService.ensureFreshQrcode(
|
||||
status.qrcodeurl || '',
|
||||
@ -2405,6 +2408,13 @@ export class QqbotNapcatLoginService {
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
if (this.toolsService.isNapcatAlreadyLoggedInError(err)) {
|
||||
return this.completeAlreadyLoggedInQuickRelogin(
|
||||
session,
|
||||
container,
|
||||
hasPasswordFallback,
|
||||
);
|
||||
}
|
||||
this.publishQuickLoginFallback(
|
||||
session,
|
||||
this.toolsService.getErrorMessage(err),
|
||||
@ -2420,6 +2430,81 @@ export class QqbotNapcatLoginService {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 NapCat WebUI 拒绝重复 quick 登录时读取真实 QQ 在线态。
|
||||
* @param session - 当前更新登录会话;成功时直接完成会话,失败时写入 fallback 进度。
|
||||
* @param container - 当前 NapCat WebUI 容器;只读调用 CheckLoginStatus/GetQQLoginInfo。
|
||||
* @param hasPasswordFallback - 是否还有密码登录分支;决定 fallback 文案里的下一步。
|
||||
* @returns 真实 QQ 在线且账号匹配时返回 true,否则返回 false 继续后续登录分支。
|
||||
*/
|
||||
private async completeAlreadyLoggedInQuickRelogin(
|
||||
session: QqbotLoginScanSession,
|
||||
container: QqbotNapcatRuntime,
|
||||
hasPasswordFallback: boolean,
|
||||
) {
|
||||
this.publishScanResultEvent(
|
||||
session,
|
||||
'quick-login-status-check',
|
||||
'processing',
|
||||
'NapCat 报告账号已登录,正在确认真实在线状态',
|
||||
);
|
||||
|
||||
let loginInfo: NapcatLoginInfo;
|
||||
try {
|
||||
const loginStatus = await this.getLoginStatus(container, true);
|
||||
if (!loginStatus.isLogin) {
|
||||
await this.syncSessionQqLoginStatus(session, loginStatus);
|
||||
this.publishQuickLoginFallback(
|
||||
session,
|
||||
loginStatus.loginError || 'NapCat 已登录标记残留但真实 QQ 已离线',
|
||||
hasPasswordFallback,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
loginInfo = await this.getLoginInfo(container);
|
||||
if (loginInfo.online === false) {
|
||||
this.publishQuickLoginFallback(
|
||||
session,
|
||||
'NapCat 已登录标记残留但真实 QQ 已离线',
|
||||
hasPasswordFallback,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const selfId = this.toolsService.pickNapcatSelfId(loginInfo);
|
||||
if (!selfId) {
|
||||
this.publishQuickLoginFallback(
|
||||
session,
|
||||
'NapCat 未返回 QQ 号',
|
||||
hasPasswordFallback,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (session.expectedSelfId && session.expectedSelfId !== selfId) {
|
||||
this.publishQuickLoginFallback(
|
||||
session,
|
||||
`当前已登录账号 ${selfId} 与目标账号 ${session.expectedSelfId} 不一致`,
|
||||
hasPasswordFallback,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
this.publishQuickLoginFallback(
|
||||
session,
|
||||
this.toolsService.getErrorMessage(err),
|
||||
hasPasswordFallback,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.completeLogin(session, container, {
|
||||
loginInfo,
|
||||
successMessage: 'NapCat 已登录,已确认真实在线状态',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 NapCat 登录运行态流程。
|
||||
* @param session - 更新登录会话;保存密码 MD5、验证码和新设备验证上下文。
|
||||
|
||||
@ -71,7 +71,7 @@ export class NapcatRuntimeProfileService {
|
||||
dataDir: input.dataDir,
|
||||
desktopProfileVersion: this.getString(
|
||||
'QQBOT_NAPCAT_DESKTOP_PROFILE_VERSION',
|
||||
'desktop-cn-v1',
|
||||
'desktop-cn-v2',
|
||||
),
|
||||
deviceIdentityId: input.deviceIdentityId,
|
||||
imageRef: this.getString('QQBOT_NAPCAT_IMAGE', ''),
|
||||
|
||||
@ -28,6 +28,8 @@ describe('NapCat Chinese Desktop Runtime image assets', () => {
|
||||
expect(dockerfile).toContain('fontconfig');
|
||||
expect(dockerfile).toContain('fc-cache -fv');
|
||||
expect(dockerfile).toContain('dbus-x11');
|
||||
expect(dockerfile).toContain('unzip');
|
||||
expect(dockerfile).toContain('zip');
|
||||
});
|
||||
|
||||
it('verifies locale, fontconfig, XDG, process user, and container hiding evidence', () => {
|
||||
@ -39,5 +41,39 @@ describe('NapCat Chinese Desktop Runtime image assets', () => {
|
||||
expect(verify).toContain('/proc/1/cgroup');
|
||||
expect(verify).toContain('XDG_CONFIG_HOME=/app/.config');
|
||||
expect(verify).toContain('Asia/Shanghai');
|
||||
expect(verify).toContain('selfInfo?.online !== false');
|
||||
expect(verify).toContain('setQQLoginStatus(false)');
|
||||
});
|
||||
|
||||
it('patches NapCat WebUI login guards to allow qrcode refresh after real QQ offline', () => {
|
||||
const dockerfile = readSource('ci/napcat-desktop-cn/Dockerfile');
|
||||
expect(dockerfile).toContain(
|
||||
'ci/napcat-desktop-cn/patches/qq-login-real-online-guard.sh',
|
||||
);
|
||||
expect(dockerfile).toContain(
|
||||
'sh /tmp/qq-login-real-online-guard.sh',
|
||||
);
|
||||
expect(dockerfile).toContain(
|
||||
"sed -i 's/\\r$//' /tmp/qq-login-real-online-guard.sh",
|
||||
);
|
||||
expect(dockerfile).toContain('NAPCAT_PATCH_ROOT=/tmp/NapCat.Shell');
|
||||
expect(dockerfile).toContain('zip -qr /app/NapCat.Shell.zip .');
|
||||
expect(dockerfile).toContain(
|
||||
'COPY ci/napcat-desktop-cn/verify.sh /ci/napcat-desktop-cn/verify.sh',
|
||||
);
|
||||
expect(dockerfile).toContain(
|
||||
"sed -i 's/\\r$//' /ci/napcat-desktop-cn/verify.sh",
|
||||
);
|
||||
|
||||
const patch = readSource(
|
||||
'ci/napcat-desktop-cn/patches/qq-login-real-online-guard.sh',
|
||||
);
|
||||
expect(patch).toContain('QQ Is Logined');
|
||||
expect(patch).toContain('getQQLoginStatus');
|
||||
expect(patch).toContain('selfInfo?.online');
|
||||
expect(patch).toContain('setQQLoginStatus(false)');
|
||||
expect(patch).toContain('RefreshQRcode');
|
||||
expect(patch).toContain('[A-Za-z_\\$]');
|
||||
expect(patch).toContain('[\\w\\$]');
|
||||
});
|
||||
});
|
||||
|
||||
@ -123,6 +123,7 @@ describe('NapCat runtime profile generation', () => {
|
||||
});
|
||||
|
||||
expect(profile).toMatchObject({
|
||||
desktopProfileVersion: 'desktop-cn-v2',
|
||||
imageRef: 'kt-napcat-desktop-cn@sha256:profiledigest',
|
||||
locale: 'zh_CN.UTF-8',
|
||||
runtimeGid: 1101,
|
||||
|
||||
@ -894,6 +894,138 @@ describe('QqbotNapcatLoginService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('confirms real login status when quick login reports the account is already logged in', async () => {
|
||||
const container = {
|
||||
baseUrl: 'http://127.0.0.1:6103/',
|
||||
id: 'container-quick-already-online',
|
||||
name: 'napcat-10001',
|
||||
};
|
||||
const accountService = {
|
||||
ensureScannedAccount: jest.fn().mockResolvedValue('account-1'),
|
||||
};
|
||||
const containerService = {
|
||||
bindAccount: jest.fn().mockResolvedValue(undefined),
|
||||
ensureRuntimeLoginEnv: jest.fn(),
|
||||
resetRuntimeLoginState: jest.fn(),
|
||||
restartRuntimeContainer: jest.fn(),
|
||||
};
|
||||
const refreshService = new QqbotNapcatLoginService(
|
||||
{ get: jest.fn() } as unknown as ConfigService,
|
||||
accountService as unknown as QqbotAccountService,
|
||||
containerService as unknown as QqbotNapcatContainerService,
|
||||
new ToolsService(),
|
||||
);
|
||||
const session = (refreshService as any).createSession({
|
||||
accountId: 'account-1',
|
||||
container,
|
||||
expectedSelfId: '10001',
|
||||
mode: 'refresh',
|
||||
preparingRelogin: true,
|
||||
status: 'pending',
|
||||
});
|
||||
(refreshService as any).sessions.set(session.id, session);
|
||||
mockWebuiLoginPost(refreshService, {
|
||||
quickError: 'QQ Is Logined',
|
||||
});
|
||||
const getLoginStatus = jest
|
||||
.spyOn(refreshService as any, 'getLoginStatus')
|
||||
.mockResolvedValue({ isLogin: true });
|
||||
jest.spyOn(refreshService as any, 'getLoginInfo').mockResolvedValue({
|
||||
nickname: 'Kwi',
|
||||
online: true,
|
||||
uin: '10001',
|
||||
});
|
||||
const refreshQrcode = jest
|
||||
.spyOn(refreshService as any, 'refreshOrGetQrcode')
|
||||
.mockResolvedValue('fallback-qrcode');
|
||||
|
||||
await (refreshService as any).prepareReloginQrcode(session, container);
|
||||
|
||||
expect(getLoginStatus).toHaveBeenCalledWith(container, true);
|
||||
expect(refreshQrcode).not.toHaveBeenCalled();
|
||||
expect(accountService.ensureScannedAccount).toHaveBeenCalledWith({
|
||||
accountId: 'account-1',
|
||||
name: 'Kwi',
|
||||
selfId: '10001',
|
||||
});
|
||||
expect(containerService.bindAccount).toHaveBeenCalledWith(
|
||||
'account-1',
|
||||
'container-quick-already-online',
|
||||
'10001',
|
||||
);
|
||||
expect(session.status).toBe('success');
|
||||
const events = (refreshService as any).sessionEventLogs.get(session.id);
|
||||
expect(events).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ step: 'quick-login-start' }),
|
||||
expect.objectContaining({
|
||||
message: 'NapCat 已登录,已确认真实在线状态',
|
||||
step: 'login-success',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps binding errors terminal after already-logged quick status is confirmed online', async () => {
|
||||
const container = {
|
||||
baseUrl: 'http://127.0.0.1:6103/',
|
||||
id: 'container-quick-already-bind-error',
|
||||
name: 'napcat-10001',
|
||||
};
|
||||
const accountService = {
|
||||
ensureScannedAccount: jest
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('账号绑定写入失败')),
|
||||
};
|
||||
const containerService = {
|
||||
bindAccount: jest.fn().mockResolvedValue(undefined),
|
||||
resetRuntimeLoginState: jest.fn(),
|
||||
restartRuntimeContainer: jest.fn(),
|
||||
};
|
||||
const refreshService = new QqbotNapcatLoginService(
|
||||
{ get: jest.fn() } as unknown as ConfigService,
|
||||
accountService as unknown as QqbotAccountService,
|
||||
containerService as unknown as QqbotNapcatContainerService,
|
||||
new ToolsService(),
|
||||
);
|
||||
const session = (refreshService as any).createSession({
|
||||
accountId: 'account-1',
|
||||
container,
|
||||
expectedSelfId: '10001',
|
||||
mode: 'refresh',
|
||||
preparingRelogin: true,
|
||||
status: 'pending',
|
||||
});
|
||||
(refreshService as any).sessions.set(session.id, session);
|
||||
mockWebuiLoginPost(refreshService, {
|
||||
quickError: 'QQ Is Logined',
|
||||
});
|
||||
jest.spyOn(refreshService as any, 'getLoginStatus').mockResolvedValue({
|
||||
isLogin: true,
|
||||
});
|
||||
jest.spyOn(refreshService as any, 'getLoginInfo').mockResolvedValue({
|
||||
nickname: 'Kwi',
|
||||
online: true,
|
||||
uin: '10001',
|
||||
});
|
||||
const refreshQrcode = jest
|
||||
.spyOn(refreshService as any, 'refreshOrGetQrcode')
|
||||
.mockResolvedValue('fallback-qrcode');
|
||||
|
||||
await (refreshService as any).prepareReloginQrcode(session, container);
|
||||
|
||||
expect(refreshQrcode).not.toHaveBeenCalled();
|
||||
expect(session.status).toBe('error');
|
||||
expect(session.qrcode).toBeUndefined();
|
||||
expect(session.errorMessage).toBe('账号绑定写入失败');
|
||||
const steps = (
|
||||
(refreshService as any).sessionEventLogs.get(session.id) ?? []
|
||||
).map((event: { step: string }) => event.step);
|
||||
expect(steps).toContain('quick-login-status-check');
|
||||
expect(steps).not.toContain('quick-login-fallback');
|
||||
expect(steps).toContain('relogin-error');
|
||||
});
|
||||
|
||||
it('does not rebuild even when the refresh session already has a rebuild count', async () => {
|
||||
const container = {
|
||||
baseUrl: 'http://127.0.0.1:6103/',
|
||||
@ -3456,6 +3588,23 @@ describe('QqbotNapcatLoginService', () => {
|
||||
expect((service as any).postNapcat).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('rejects stale status qrcode when NapCat still blocks qrcode generation as already logged in', async () => {
|
||||
jest
|
||||
.spyOn(service as any, 'postNapcat')
|
||||
.mockRejectedValue(new Error('QQ Is Logined'));
|
||||
jest.spyOn(service as any, 'getLoginStatus').mockResolvedValue({
|
||||
isLogin: false,
|
||||
isOffline: true,
|
||||
qrcodeurl: 'old-status-qrcode',
|
||||
});
|
||||
|
||||
await expect(
|
||||
(service as any).getQrcode({ id: 'container-stale-login-guard' }, false, {
|
||||
requireFresh: true,
|
||||
}),
|
||||
).rejects.toThrow('NapCat WebUI 登录态仍阻止生成新二维码');
|
||||
});
|
||||
|
||||
it('does not replace current qrcode with expired status qrcode', async () => {
|
||||
(service as any).sessions.set('session-2', {
|
||||
containerId: 'container-2',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user