700 lines
21 KiB
TypeScript
700 lines
21 KiB
TypeScript
import { ToolsService } from '@/common';
|
||
import { QqbotAccountService } from '@/qqbot/account/qqbot-account.service';
|
||
|
||
describe('QqbotAccountService', () => {
|
||
it('stores NapCat login password as encrypted secret and never persists the transport field', async () => {
|
||
const toolsService = new ToolsService();
|
||
const accountRepository = {
|
||
create: jest.fn((input) => input),
|
||
findOne: jest.fn().mockResolvedValue(null),
|
||
save: jest.fn(async (input) => ({ ...input, id: 'account-1' })),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
toolsService,
|
||
undefined,
|
||
{
|
||
get: jest.fn((key: string) =>
|
||
key === 'QQBOT_ACCOUNT_SECRET_KEY' ? 'unit-secret' : '',
|
||
),
|
||
} as any,
|
||
{
|
||
decryptPassword: jest.fn().mockReturnValue('qq-login-password'),
|
||
} as any,
|
||
);
|
||
|
||
await service.save({
|
||
encryptedLoginPassword: 'encrypted-payload',
|
||
selfId: '1914728559',
|
||
});
|
||
|
||
const payload = accountRepository.create.mock.calls[0][0];
|
||
expect(payload.encryptedLoginPassword).toBeUndefined();
|
||
expect(payload.napcatLoginPasswordSecret).toBeTruthy();
|
||
expect(payload.napcatLoginPasswordSecret).not.toContain(
|
||
'qq-login-password',
|
||
);
|
||
expect(
|
||
toolsService.decryptSecretText(
|
||
payload.napcatLoginPasswordSecret,
|
||
'unit-secret',
|
||
),
|
||
).toBe('qq-login-password');
|
||
});
|
||
|
||
it('requires an explicit secret key before storing NapCat login password', async () => {
|
||
const accountRepository = {
|
||
create: jest.fn((input) => input),
|
||
findOne: jest.fn().mockResolvedValue(null),
|
||
save: jest.fn(async (input) => ({ ...input, id: 'account-1' })),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
new ToolsService(),
|
||
undefined,
|
||
{
|
||
get: jest.fn().mockReturnValue(''),
|
||
} as any,
|
||
{
|
||
decryptPassword: jest.fn().mockReturnValue('qq-login-password'),
|
||
} as any,
|
||
);
|
||
|
||
await expect(
|
||
service.save({
|
||
encryptedLoginPassword: 'encrypted-payload',
|
||
selfId: '1914728559',
|
||
}),
|
||
).rejects.toMatchObject({
|
||
response: expect.objectContaining({
|
||
msg: 'QQBot 账号登录密码密钥未配置,请设置 QQBOT_ACCOUNT_SECRET_KEY 或 ADMIN_TOKEN_SECRET',
|
||
}),
|
||
});
|
||
expect(accountRepository.save).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it('rejects public placeholder secret before storing NapCat login password', async () => {
|
||
const accountRepository = {
|
||
create: jest.fn((input) => input),
|
||
findOne: jest.fn().mockResolvedValue(null),
|
||
save: jest.fn(async (input) => ({ ...input, id: 'account-1' })),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
new ToolsService(),
|
||
undefined,
|
||
{
|
||
get: jest.fn((key: string) =>
|
||
key === 'ADMIN_TOKEN_SECRET' ? 'change-me' : '',
|
||
),
|
||
} as any,
|
||
{
|
||
decryptPassword: jest.fn().mockReturnValue('qq-login-password'),
|
||
} as any,
|
||
);
|
||
|
||
await expect(
|
||
service.save({
|
||
encryptedLoginPassword: 'encrypted-payload',
|
||
selfId: '1914728559',
|
||
}),
|
||
).rejects.toMatchObject({
|
||
response: expect.objectContaining({
|
||
msg: 'QQBot 账号登录密码密钥未配置,请设置 QQBOT_ACCOUNT_SECRET_KEY 或 ADMIN_TOKEN_SECRET',
|
||
}),
|
||
});
|
||
expect(accountRepository.save).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it('preserves NapCat login password whitespace after decryption', async () => {
|
||
const toolsService = new ToolsService();
|
||
const accountRepository = {
|
||
create: jest.fn((input) => input),
|
||
findOne: jest.fn().mockResolvedValue(null),
|
||
save: jest.fn(async (input) => ({ ...input, id: 'account-1' })),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
toolsService,
|
||
undefined,
|
||
{
|
||
get: jest.fn((key: string) =>
|
||
key === 'QQBOT_ACCOUNT_SECRET_KEY' ? 'unit-secret' : '',
|
||
),
|
||
} as any,
|
||
{
|
||
decryptPassword: jest.fn().mockReturnValue(' qq-login-password '),
|
||
} as any,
|
||
);
|
||
|
||
await service.save({
|
||
encryptedLoginPassword: 'encrypted-payload',
|
||
selfId: '1914728559',
|
||
});
|
||
|
||
const payload = accountRepository.create.mock.calls[0][0];
|
||
expect(
|
||
toolsService.decryptSecretText(
|
||
payload.napcatLoginPasswordSecret,
|
||
'unit-secret',
|
||
),
|
||
).toBe(' qq-login-password ');
|
||
});
|
||
|
||
it('does not update NapCat login password when edit leaves the password blank', async () => {
|
||
const accountRepository = {
|
||
findOne: jest.fn().mockResolvedValue({ id: 'account-1' }),
|
||
update: jest.fn(),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{ update: jest.fn() } as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
new ToolsService(),
|
||
);
|
||
|
||
await service.update({
|
||
id: 'account-1',
|
||
name: 'Mirror',
|
||
selfId: '1914728559',
|
||
});
|
||
|
||
expect(accountRepository.update).toHaveBeenCalledWith(
|
||
{ id: 'account-1' },
|
||
expect.not.objectContaining({
|
||
encryptedLoginPassword: expect.anything(),
|
||
napcatLoginPasswordSecret: expect.anything(),
|
||
}),
|
||
);
|
||
});
|
||
|
||
it('preserves previous offline reason when later disconnect has no explicit error', async () => {
|
||
const accountRepository = {
|
||
update: jest.fn(),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
new ToolsService(),
|
||
);
|
||
|
||
await service.markOffline('1914728559');
|
||
|
||
expect(accountRepository.update).toHaveBeenCalledWith(
|
||
{ selfId: '1914728559' },
|
||
{
|
||
connectStatus: 'offline',
|
||
},
|
||
);
|
||
});
|
||
|
||
it('truncates offline reason before writing lastError column', async () => {
|
||
const accountRepository = {
|
||
update: jest.fn(),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
{} as any,
|
||
new ToolsService(),
|
||
);
|
||
|
||
await service.markOffline('1914728559', '错误'.repeat(300));
|
||
|
||
expect(accountRepository.update).toHaveBeenCalledWith(
|
||
{ selfId: '1914728559' },
|
||
{
|
||
connectStatus: 'offline',
|
||
lastError: `${'错误'.repeat(248)}错...`,
|
||
},
|
||
);
|
||
});
|
||
|
||
it('syncs NapCat runtime offline logs back to account list status', async () => {
|
||
const account = {
|
||
connectStatus: 'online',
|
||
enabled: true,
|
||
id: 'account-1',
|
||
isDeleted: false,
|
||
lastError: null,
|
||
name: '主账号',
|
||
selfId: '1914728559',
|
||
};
|
||
const binding = {
|
||
accountId: 'account-1',
|
||
bindStatus: 'bound',
|
||
containerId: 'container-1',
|
||
isDeleted: false,
|
||
isPrimary: true,
|
||
lastLoginAt: new Date('2026-06-10T12:00:00.000Z'),
|
||
};
|
||
const container = {
|
||
id: 'container-1',
|
||
isDeleted: false,
|
||
lastError: null,
|
||
name: 'kt-qqbot-napcat-1914728559',
|
||
status: 'running',
|
||
webuiPort: 6101,
|
||
};
|
||
const createAccountBuilder = () => ({
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getManyAndCount: jest.fn().mockResolvedValue([[account], 1]),
|
||
orderBy: jest.fn().mockReturnThis(),
|
||
skip: jest.fn().mockReturnThis(),
|
||
take: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
});
|
||
const createBindingBuilder = () => ({
|
||
addOrderBy: jest.fn().mockReturnThis(),
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([binding]),
|
||
orderBy: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
});
|
||
const createContainerBuilder = () => ({
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([container]),
|
||
where: jest.fn().mockReturnThis(),
|
||
});
|
||
const accountRepository = {
|
||
createQueryBuilder: jest.fn(createAccountBuilder),
|
||
update: jest.fn(),
|
||
};
|
||
const napcatContainerService = {
|
||
detectRuntimeOffline: jest
|
||
.fn()
|
||
.mockResolvedValue('NapCat 账号状态变更为离线'),
|
||
};
|
||
const systemNoticePublisher = {
|
||
publishSystemNotice: jest.fn().mockResolvedValue('notice-1'),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{ createQueryBuilder: jest.fn(createBindingBuilder) } as any,
|
||
{ createQueryBuilder: jest.fn(createContainerBuilder) } as any,
|
||
napcatContainerService as any,
|
||
new ToolsService(),
|
||
systemNoticePublisher as any,
|
||
);
|
||
|
||
const page = await service.page({});
|
||
|
||
expect(accountRepository.update).toHaveBeenCalledWith(
|
||
{ selfId: '1914728559' },
|
||
{
|
||
connectStatus: 'offline',
|
||
lastError: 'NapCat 账号状态变更为离线',
|
||
},
|
||
);
|
||
expect(page.list[0]).toEqual(
|
||
expect.objectContaining({
|
||
connectStatus: 'offline',
|
||
lastError: 'NapCat 账号状态变更为离线',
|
||
}),
|
||
);
|
||
expect(systemNoticePublisher.publishSystemNotice).toHaveBeenCalledWith(
|
||
expect.objectContaining({
|
||
content: 'NapCat 账号状态变更为离线',
|
||
dedupeKey: 'qqbot:offline:1914728559',
|
||
eventType: 'qqbot.account.offline',
|
||
notifyRoleCode: 'super',
|
||
severity: 'error',
|
||
source: 'qqbot',
|
||
title: 'QQBot 账号已下线:1914728559',
|
||
}),
|
||
);
|
||
});
|
||
|
||
it('does not re-read NapCat logs when the container was checked recently', async () => {
|
||
const account = {
|
||
connectStatus: 'online',
|
||
enabled: true,
|
||
id: 'account-1',
|
||
isDeleted: false,
|
||
lastError: null,
|
||
name: '主账号',
|
||
selfId: '1914728559',
|
||
};
|
||
const binding = {
|
||
accountId: 'account-1',
|
||
bindStatus: 'bound',
|
||
containerId: 'container-1',
|
||
isDeleted: false,
|
||
isPrimary: true,
|
||
lastLoginAt: new Date('2026-06-10T12:00:00.000Z'),
|
||
};
|
||
const container = {
|
||
id: 'container-1',
|
||
isDeleted: false,
|
||
lastCheckedAt: new Date(),
|
||
lastError: null,
|
||
name: 'kt-qqbot-napcat-1914728559',
|
||
status: 'running',
|
||
webuiPort: 6101,
|
||
};
|
||
const accountRepository = {
|
||
createQueryBuilder: jest.fn(() => ({
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getManyAndCount: jest.fn().mockResolvedValue([[account], 1]),
|
||
orderBy: jest.fn().mockReturnThis(),
|
||
skip: jest.fn().mockReturnThis(),
|
||
take: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
update: jest.fn(),
|
||
};
|
||
const napcatContainerService = {
|
||
detectRuntimeOffline: jest.fn(),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{
|
||
createQueryBuilder: jest.fn(() => ({
|
||
addOrderBy: jest.fn().mockReturnThis(),
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([binding]),
|
||
orderBy: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
} as any,
|
||
{
|
||
createQueryBuilder: jest.fn(() => ({
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([container]),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
} as any,
|
||
napcatContainerService as any,
|
||
new ToolsService(),
|
||
);
|
||
|
||
const page = await service.page({});
|
||
|
||
expect(napcatContainerService.detectRuntimeOffline).not.toHaveBeenCalled();
|
||
expect(accountRepository.update).not.toHaveBeenCalled();
|
||
expect(page.list[0]).toEqual(
|
||
expect.objectContaining({
|
||
connectStatus: 'online',
|
||
napcat: expect.objectContaining({
|
||
lastCheckedAt: container.lastCheckedAt,
|
||
}),
|
||
}),
|
||
);
|
||
});
|
||
|
||
it('ignores cached NapCat offline reason after the account reconnects', async () => {
|
||
const checkedAt = new Date('2026-06-11T02:00:00.000Z');
|
||
const account = {
|
||
connectStatus: 'online',
|
||
enabled: true,
|
||
id: 'account-1',
|
||
isDeleted: false,
|
||
lastConnectedAt: new Date('2026-06-11T02:00:10.000Z'),
|
||
lastError: null,
|
||
name: '主账号',
|
||
selfId: '1914728559',
|
||
};
|
||
const binding = {
|
||
accountId: 'account-1',
|
||
bindStatus: 'bound',
|
||
containerId: 'container-1',
|
||
isDeleted: false,
|
||
isPrimary: true,
|
||
lastLoginAt: checkedAt,
|
||
};
|
||
const container = {
|
||
id: 'container-1',
|
||
isDeleted: false,
|
||
lastCheckedAt: checkedAt,
|
||
lastError: '账号状态变更为离线',
|
||
name: 'kt-qqbot-napcat-1914728559',
|
||
status: 'running',
|
||
webuiPort: 6101,
|
||
};
|
||
const accountRepository = {
|
||
createQueryBuilder: jest.fn(() => ({
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getManyAndCount: jest.fn().mockResolvedValue([[account], 1]),
|
||
orderBy: jest.fn().mockReturnThis(),
|
||
skip: jest.fn().mockReturnThis(),
|
||
take: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
update: jest.fn(),
|
||
};
|
||
const napcatContainerService = {
|
||
detectRuntimeOffline: jest.fn(),
|
||
};
|
||
const systemNoticePublisher = {
|
||
publishSystemNotice: jest.fn().mockResolvedValue(undefined),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{
|
||
createQueryBuilder: jest.fn(() => ({
|
||
addOrderBy: jest.fn().mockReturnThis(),
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([binding]),
|
||
orderBy: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
} as any,
|
||
{
|
||
createQueryBuilder: jest.fn(() => ({
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([container]),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
} as any,
|
||
napcatContainerService as any,
|
||
new ToolsService(),
|
||
systemNoticePublisher as any,
|
||
);
|
||
|
||
const page = await service.page({});
|
||
|
||
expect(napcatContainerService.detectRuntimeOffline).not.toHaveBeenCalled();
|
||
expect(accountRepository.update).not.toHaveBeenCalled();
|
||
expect(systemNoticePublisher.publishSystemNotice).not.toHaveBeenCalled();
|
||
expect(page.list[0]).toEqual(
|
||
expect.objectContaining({
|
||
connectStatus: 'online',
|
||
lastError: null,
|
||
}),
|
||
);
|
||
});
|
||
|
||
it('lets watchdog auto-login before marking an account offline', async () => {
|
||
const toolsService = new ToolsService();
|
||
const account = {
|
||
connectStatus: 'online',
|
||
enabled: true,
|
||
id: 'account-1',
|
||
isDeleted: false,
|
||
lastError: null,
|
||
name: '主账号',
|
||
napcatLoginPasswordSecret: toolsService.encryptSecretText(
|
||
'qq-login-password',
|
||
'unit-secret',
|
||
),
|
||
selfId: '1914728559',
|
||
};
|
||
const binding = {
|
||
accountId: 'account-1',
|
||
bindStatus: 'bound',
|
||
containerId: 'container-1',
|
||
isDeleted: false,
|
||
isPrimary: true,
|
||
lastLoginAt: new Date('2026-06-10T12:00:00.000Z'),
|
||
};
|
||
const container = {
|
||
id: 'container-1',
|
||
isDeleted: false,
|
||
lastError: null,
|
||
name: 'kt-qqbot-napcat-1914728559',
|
||
status: 'running',
|
||
webuiPort: 6101,
|
||
};
|
||
const accountRepository = {
|
||
createQueryBuilder: jest.fn(() => ({
|
||
addSelect: jest.fn().mockReturnThis(),
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([account]),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
update: jest.fn(),
|
||
};
|
||
const napcatContainerService = {
|
||
detectRuntimeOffline: jest
|
||
.fn()
|
||
.mockResolvedValue('NapCat 账号状态变更为离线'),
|
||
tryAutoLogin: jest.fn().mockResolvedValue({
|
||
method: 'password',
|
||
success: true,
|
||
}),
|
||
};
|
||
const systemNoticePublisher = {
|
||
publishSystemNotice: jest.fn().mockResolvedValue(undefined),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{
|
||
createQueryBuilder: jest.fn(() => ({
|
||
addOrderBy: jest.fn().mockReturnThis(),
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([binding]),
|
||
orderBy: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
} as any,
|
||
{
|
||
createQueryBuilder: jest.fn(() => ({
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([container]),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
} as any,
|
||
napcatContainerService as any,
|
||
toolsService,
|
||
systemNoticePublisher as any,
|
||
{
|
||
get: jest.fn((key: string) =>
|
||
key === 'QQBOT_ACCOUNT_SECRET_KEY' ? 'unit-secret' : '',
|
||
),
|
||
} as any,
|
||
);
|
||
|
||
const result = await service.runOfflineWatchdog();
|
||
|
||
expect(result).toEqual({ checked: 1 });
|
||
expect(napcatContainerService.tryAutoLogin).toHaveBeenCalledWith(
|
||
container,
|
||
{
|
||
loginPassword: 'qq-login-password',
|
||
selfId: '1914728559',
|
||
},
|
||
);
|
||
expect(accountRepository.update).toHaveBeenCalledWith(
|
||
{ selfId: '1914728559' },
|
||
expect.objectContaining({
|
||
clientRole: 'Universal',
|
||
connectStatus: 'online',
|
||
lastConnectedAt: expect.any(Date),
|
||
lastError: null,
|
||
}),
|
||
);
|
||
expect(systemNoticePublisher.publishSystemNotice).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it('records a cleanup failure when watchdog auto-login leaves runtime password env uncertain', async () => {
|
||
const toolsService = new ToolsService();
|
||
const account = {
|
||
connectStatus: 'online',
|
||
enabled: true,
|
||
id: 'account-1',
|
||
isDeleted: false,
|
||
lastError: null,
|
||
name: '主账号',
|
||
napcatLoginPasswordSecret: toolsService.encryptSecretText(
|
||
'qq-login-password',
|
||
'unit-secret',
|
||
),
|
||
selfId: '1914728559',
|
||
};
|
||
const binding = {
|
||
accountId: 'account-1',
|
||
bindStatus: 'bound',
|
||
containerId: 'container-1',
|
||
isDeleted: false,
|
||
isPrimary: true,
|
||
lastLoginAt: new Date('2026-06-10T12:00:00.000Z'),
|
||
};
|
||
const container = {
|
||
id: 'container-1',
|
||
isDeleted: false,
|
||
lastError: null,
|
||
name: 'kt-qqbot-napcat-1914728559',
|
||
status: 'running',
|
||
webuiPort: 6101,
|
||
};
|
||
const accountRepository = {
|
||
createQueryBuilder: jest.fn(() => ({
|
||
addSelect: jest.fn().mockReturnThis(),
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([account]),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
update: jest.fn(),
|
||
};
|
||
const napcatContainerService = {
|
||
detectRuntimeOffline: jest
|
||
.fn()
|
||
.mockResolvedValue('NapCat 账号状态变更为离线'),
|
||
tryAutoLogin: jest.fn().mockResolvedValue({
|
||
cleanupFailed: true,
|
||
success: false,
|
||
}),
|
||
};
|
||
const systemNoticePublisher = {
|
||
publishSystemNotice: jest.fn().mockResolvedValue(undefined),
|
||
};
|
||
const service = new QqbotAccountService(
|
||
accountRepository as any,
|
||
{} as any,
|
||
{
|
||
createQueryBuilder: jest.fn(() => ({
|
||
addOrderBy: jest.fn().mockReturnThis(),
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([binding]),
|
||
orderBy: jest.fn().mockReturnThis(),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
} as any,
|
||
{
|
||
createQueryBuilder: jest.fn(() => ({
|
||
andWhere: jest.fn().mockReturnThis(),
|
||
getMany: jest.fn().mockResolvedValue([container]),
|
||
where: jest.fn().mockReturnThis(),
|
||
})),
|
||
} as any,
|
||
napcatContainerService as any,
|
||
toolsService,
|
||
systemNoticePublisher as any,
|
||
{
|
||
get: jest.fn((key: string) =>
|
||
key === 'QQBOT_ACCOUNT_SECRET_KEY' ? 'unit-secret' : '',
|
||
),
|
||
} as any,
|
||
);
|
||
|
||
await service.runOfflineWatchdog();
|
||
|
||
expect(accountRepository.update).toHaveBeenCalledTimes(1);
|
||
expect(accountRepository.update).toHaveBeenCalledWith(
|
||
{ selfId: '1914728559' },
|
||
{
|
||
connectStatus: 'offline',
|
||
lastError: 'NapCat 自动登录后运行态密码清理失败,请手动更新登录',
|
||
},
|
||
);
|
||
expect(systemNoticePublisher.publishSystemNotice).toHaveBeenCalledWith(
|
||
expect.objectContaining({
|
||
content: 'NapCat 自动登录后运行态密码清理失败,请手动更新登录',
|
||
eventType: 'qqbot.account.offline',
|
||
severity: 'error',
|
||
title: 'QQBot 账号已下线:1914728559',
|
||
}),
|
||
);
|
||
expect(
|
||
JSON.stringify(accountRepository.update.mock.calls),
|
||
).not.toContain('NapCat 账号状态变更为离线');
|
||
});
|
||
});
|