From edb77bd67c1fbb01680adcca269ba462a8d9a760 Mon Sep 17 00:00:00 2001 From: sunlei Date: Fri, 24 Jul 2026 14:53:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9A=E4=B9=89=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E5=89=8D=E7=AB=AF=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antdv-next/src/api/core/menu.spec.ts | 53 +++ apps/web-antdv-next/src/api/core/menu.ts | 18 + .../src/api/qqbot/message-push.spec.ts | 224 +++++++++ .../src/api/qqbot/message-push.ts | 447 ++++++++++++++++++ .../src/router/routes/modules/qqbot.spec.ts | 34 ++ .../src/router/routes/modules/qqbot.ts | 18 + 6 files changed, 794 insertions(+) create mode 100644 apps/web-antdv-next/src/api/qqbot/message-push.spec.ts create mode 100644 apps/web-antdv-next/src/api/qqbot/message-push.ts create mode 100644 apps/web-antdv-next/src/router/routes/modules/qqbot.spec.ts diff --git a/apps/web-antdv-next/src/api/core/menu.spec.ts b/apps/web-antdv-next/src/api/core/menu.spec.ts index 55a363e..330f5b0 100644 --- a/apps/web-antdv-next/src/api/core/menu.spec.ts +++ b/apps/web-antdv-next/src/api/core/menu.spec.ts @@ -391,4 +391,57 @@ describe('core menu api', () => { }, ]); }); + + it('keeps every supported message-push menu name once while filtering unknown nodes', async () => { + const messagePushNames = [ + 'QqBotMessageSubscription', + 'QqBotMessageTemplate', + 'QqBotMessageSubscriptionList', + 'QqBotMessageSubscriptionCreate', + 'QqBotMessageSubscriptionUpdate', + 'QqBotMessageSubscriptionDelete', + 'QqBotMessageSubscriptionToggle', + 'QqBotMessageTemplateList', + 'QqBotMessageTemplateCreate', + 'QqBotMessageTemplateUpdate', + 'QqBotMessageTemplateDelete', + 'QqBotMessageTemplateToggle', + 'QqBotMessageTemplatePreview', + 'QqBotAccountMessagePushList', + 'QqBotAccountMessagePushCreate', + 'QqBotAccountMessagePushUpdate', + 'QqBotAccountMessagePushDelete', + 'QqBotAccountMessagePushToggle', + ]; + requestClientGet.mockResolvedValue([ + { + name: 'QqBot', + path: '/qqbot', + children: [ + { + name: 'QqBotAccount', + path: '/qqbot/account', + }, + ...messagePushNames.map((name) => ({ + authCode: `QqBot:${name}`, + name, + type: 'button', + })), + { + name: 'UnsupportedMessagePushNode', + type: 'button', + }, + ], + }, + ]); + + const { getAllMenusApi } = await import('./menu'); + const menus = await getAllMenusApi(); + const qqbot = menus.find((menu) => menu.name === 'QqBot'); + const retainedNames = qqbot?.children?.map((child) => child.name) ?? []; + + expect(retainedNames).toEqual(['QqBotAccount', ...messagePushNames]); + expect(new Set(retainedNames).size).toBe(retainedNames.length); + expect(retainedNames).not.toContain('UnsupportedMessagePushNode'); + }); }); diff --git a/apps/web-antdv-next/src/api/core/menu.ts b/apps/web-antdv-next/src/api/core/menu.ts index 1d3d278..3b9dbf7 100644 --- a/apps/web-antdv-next/src/api/core/menu.ts +++ b/apps/web-antdv-next/src/api/core/menu.ts @@ -33,6 +33,11 @@ const SUPPORTED_ADMIN_MENU_NAMES = new Set([ 'QqBotAccountDelete', 'QqBotAccountEdit', 'QqBotAccountKick', + 'QqBotAccountMessagePushCreate', + 'QqBotAccountMessagePushDelete', + 'QqBotAccountMessagePushList', + 'QqBotAccountMessagePushToggle', + 'QqBotAccountMessagePushUpdate', 'QqBotAccountNapcatWebui', 'QqBotAccountRefreshLogin', 'QqBotAccountWebUI', @@ -45,6 +50,19 @@ const SUPPORTED_ADMIN_MENU_NAMES = new Set([ 'QqBotConversation', 'QqBotDashboard', 'QqBotMessage', + 'QqBotMessageSubscription', + 'QqBotMessageSubscriptionCreate', + 'QqBotMessageSubscriptionDelete', + 'QqBotMessageSubscriptionList', + 'QqBotMessageSubscriptionToggle', + 'QqBotMessageSubscriptionUpdate', + 'QqBotMessageTemplate', + 'QqBotMessageTemplateCreate', + 'QqBotMessageTemplateDelete', + 'QqBotMessageTemplateList', + 'QqBotMessageTemplatePreview', + 'QqBotMessageTemplateToggle', + 'QqBotMessageTemplateUpdate', 'QqBotPermission', 'QqBotPermissionCreate', 'QqBotPermissionDelete', diff --git a/apps/web-antdv-next/src/api/qqbot/message-push.spec.ts b/apps/web-antdv-next/src/api/qqbot/message-push.spec.ts new file mode 100644 index 0000000..b65a928 --- /dev/null +++ b/apps/web-antdv-next/src/api/qqbot/message-push.spec.ts @@ -0,0 +1,224 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { requestClient } from '#/api/request'; + +import * as messagePushApi from './message-push'; +import { + createAccountMessagePushBinding, + createMessageSubscription, + createMessageTemplate, + deleteAccountMessagePushBinding, + deleteMessageSubscription, + deleteMessageTemplate, + getAccountMessagePushBindings, + getAccountMessagePushTargets, + getMessagePushSourceDetail, + getMessagePushSources, + getMessageSubscriptionList, + getMessageTemplateList, + getStunMappingPortChangedOptions, + previewMessageTemplate, + setAccountMessagePushBindingEnabled, + setMessageSubscriptionEnabled, + setMessageTemplateEnabled, + updateAccountMessagePushBinding, + updateMessageSubscription, + updateMessageTemplate, +} from './message-push'; + +vi.mock('#/api/request', () => ({ + requestClient: { + delete: vi.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + }, +})); + +describe('qqbot message push api', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('owns the exact source, subscription, and template callers', async () => { + const sourceKey = 'network.stun/a b'; + const id = '10000000000000001'; + const previewContent = '端口 $' + '{{port}}'; + const subscriptionInput = { + enabled: true, + name: 'STUN 端口变更', + sourceConfig: { + ddnsRecordId: '10000000000000003', + portForwardId: '10000000000000002', + }, + sourceKey: 'network.stun.mapping-port-changed', + }; + const templateInput = { + content: previewContent, + enabled: true, + name: '端口通知', + sourceKey: 'network.stun.mapping-port-changed', + }; + const subscriptionQuery = { + enabled: true, + name: 'STUN', + pageNo: 1, + pageSize: 10, + sourceKey: 'network.stun.mapping-port-changed', + }; + const templateQuery = { + enabled: false, + name: '通知', + pageNo: 2, + pageSize: 20, + sourceKey: 'network.stun.mapping-port-changed', + }; + + await getMessagePushSources(); + await getMessagePushSourceDetail(sourceKey); + await getStunMappingPortChangedOptions(); + await getMessageSubscriptionList(subscriptionQuery); + await createMessageSubscription(subscriptionInput); + await updateMessageSubscription('id/a b', subscriptionInput); + await setMessageSubscriptionEnabled(id, false); + await deleteMessageSubscription('id/a b'); + await getMessageTemplateList(templateQuery); + await createMessageTemplate(templateInput); + await updateMessageTemplate('template/a b', templateInput); + await setMessageTemplateEnabled(id, false); + await deleteMessageTemplate('template/a b'); + await previewMessageTemplate({ + content: previewContent, + sourceKey: 'network.stun.mapping-port-changed', + }); + + expect(requestClient.get).toHaveBeenNthCalledWith( + 1, + '/qqbot/message-push/sources', + ); + expect(requestClient.get).toHaveBeenNthCalledWith( + 2, + '/qqbot/message-push/sources/network.stun%2Fa%20b', + ); + expect(requestClient.get).toHaveBeenNthCalledWith( + 3, + '/qqbot/message-push/sources/network.stun.mapping-port-changed/options', + ); + expect(requestClient.get).toHaveBeenNthCalledWith( + 4, + '/qqbot/message-push/subscriptions', + { params: subscriptionQuery }, + ); + expect(requestClient.get).toHaveBeenNthCalledWith( + 5, + '/qqbot/message-push/templates', + { params: templateQuery }, + ); + expect(requestClient.post).toHaveBeenNthCalledWith( + 1, + '/qqbot/message-push/subscriptions', + subscriptionInput, + ); + expect(requestClient.post).toHaveBeenNthCalledWith( + 2, + '/qqbot/message-push/templates', + templateInput, + ); + expect(requestClient.post).toHaveBeenNthCalledWith( + 3, + '/qqbot/message-push/templates/preview', + { + content: previewContent, + sourceKey: 'network.stun.mapping-port-changed', + }, + ); + expect(requestClient.put).toHaveBeenNthCalledWith( + 1, + '/qqbot/message-push/subscriptions/id%2Fa%20b', + subscriptionInput, + ); + expect(requestClient.put).toHaveBeenNthCalledWith( + 2, + '/qqbot/message-push/subscriptions/10000000000000001/enabled', + { enabled: false }, + ); + expect(requestClient.put).toHaveBeenNthCalledWith( + 3, + '/qqbot/message-push/templates/template%2Fa%20b', + templateInput, + ); + expect(requestClient.put).toHaveBeenNthCalledWith( + 4, + '/qqbot/message-push/templates/10000000000000001/enabled', + { enabled: false }, + ); + expect(requestClient.delete).toHaveBeenNthCalledWith( + 1, + '/qqbot/message-push/subscriptions/id%2Fa%20b', + ); + expect(requestClient.delete).toHaveBeenNthCalledWith( + 2, + '/qqbot/message-push/templates/template%2Fa%20b', + ); + }); + + it('keeps account IDs as strings and encodes every account path segment', async () => { + const selfId = '10000000000000001/a b'; + const bindingId = 'binding/a b'; + const bindingInput = { + enabled: true, + subscriptionId: '10000000000000002', + targets: [ + { + targetId: '10000000000000003', + targetName: '测试群', + targetType: 'group' as const, + }, + ], + templateId: '10000000000000004', + }; + + await getAccountMessagePushBindings(selfId); + await createAccountMessagePushBinding(selfId, bindingInput); + await updateAccountMessagePushBinding(selfId, bindingId, bindingInput); + await setAccountMessagePushBindingEnabled(selfId, bindingId, false); + await deleteAccountMessagePushBinding(selfId, bindingId); + await getAccountMessagePushTargets(selfId); + + const accountPath = + '/qqbot/accounts/10000000000000001%2Fa%20b/message-push'; + expect(requestClient.get).toHaveBeenNthCalledWith( + 1, + `${accountPath}/bindings`, + ); + expect(requestClient.get).toHaveBeenNthCalledWith( + 2, + `${accountPath}/targets`, + ); + expect(requestClient.post).toHaveBeenCalledWith( + `${accountPath}/bindings`, + bindingInput, + ); + expect(requestClient.put).toHaveBeenNthCalledWith( + 1, + `${accountPath}/bindings/binding%2Fa%20b`, + bindingInput, + ); + expect(requestClient.put).toHaveBeenNthCalledWith( + 2, + `${accountPath}/bindings/binding%2Fa%20b/enabled`, + { enabled: false }, + ); + expect(requestClient.delete).toHaveBeenCalledWith( + `${accountPath}/bindings/binding%2Fa%20b`, + ); + }); + + it('does not expose internal publish, event, delivery, or worker callers', () => { + expect(Object.keys(messagePushApi)).not.toEqual( + expect.arrayContaining([ + expect.stringMatching(/delivery|event|publish|worker/i), + ]), + ); + }); +}); diff --git a/apps/web-antdv-next/src/api/qqbot/message-push.ts b/apps/web-antdv-next/src/api/qqbot/message-push.ts new file mode 100644 index 0000000..5a3fe69 --- /dev/null +++ b/apps/web-antdv-next/src/api/qqbot/message-push.ts @@ -0,0 +1,447 @@ +import { requestClient } from '#/api/request'; + +export namespace QqbotMessagePushApi { + export type SystemMessageScalar = boolean | null | number | string; + + export type QqbotMessagePushTargetType = 'group' | 'private'; + + export interface SystemMessageSourceVariableDefinition { + description: string; + example: string; + key: string; + label: string; + type: 'boolean' | 'number' | 'string'; + } + + export interface SystemMessageSourceFieldDefinition { + dependsOn?: string; + key: string; + label: string; + optionCollection: 'ddnsRecords' | 'portForwards'; + required: true; + type: 'select'; + } + + export interface SystemMessageSourceDefinition { + description: string; + displayName: string; + sourceKey: string; + subscriptionFields: SystemMessageSourceFieldDefinition[]; + variables: SystemMessageSourceVariableDefinition[]; + version: 1; + } + + export interface StunMappingPortChangedSubscriptionConfig { + ddnsRecordId: string; + portForwardId: string; + } + + export interface StunMappingPortChangedOptionsResponse { + ddnsRecords: Array<{ + disabledReasonCode: null | string; + eligible: boolean; + fqdn: string; + id: string; + name: string; + portForwardId: string; + }>; + portForwards: Array<{ + disabledReasonCode: null | string; + eligible: boolean; + externalPort: number; + id: string; + internalPort: number; + name: string; + protocol: 'tcp' | 'udp'; + }>; + } + + export interface MessageSubscriptionView { + createTime: string; + enabled: boolean; + id: string; + invalidReasonCode: null | string; + name: string; + remark: null | string; + sourceConfig: StunMappingPortChangedSubscriptionConfig; + sourceKey: string; + sourceName: string; + sourceSummary: string; + updateTime: string; + valid: boolean; + } + + export interface MessageSubscriptionListQuery { + enabled?: boolean; + name?: string; + pageNo?: number; + pageSize?: number; + sourceKey?: string; + } + + export interface MessageSubscriptionInput { + enabled: boolean; + name: string; + remark?: string; + sourceConfig: Record; + sourceKey: string; + } + + export interface MessageTemplateView { + content: string; + createTime: string; + enabled: boolean; + id: string; + name: string; + referenceCount: number; + remark: null | string; + sourceKey: string; + sourceName: string; + updateTime: string; + } + + export interface MessageTemplateListQuery { + enabled?: boolean; + name?: string; + pageNo?: number; + pageSize?: number; + sourceKey?: string; + } + + export interface MessageTemplateInput { + content: string; + enabled: boolean; + name: string; + remark?: string; + sourceKey: string; + } + + export interface MessageTemplatePreviewInput { + content: string; + sourceKey: string; + } + + export interface MessageTemplatePreview { + renderedMessage: string; + variables: Record; + } + + export interface QqbotMessagePublishTargetInput { + targetId: string; + targetName?: string; + targetType: QqbotMessagePushTargetType; + } + + export interface QqbotMessagePublishBindingInput { + enabled: boolean; + subscriptionId: string; + targets: QqbotMessagePublishTargetInput[]; + templateId: string; + } + + export interface QqbotMessagePushTargetOption { + label: string; + targetId: string; + targetType: QqbotMessagePushTargetType; + } + + export interface QqbotMessagePushTargetOptionsResponse { + available: boolean; + options: QqbotMessagePushTargetOption[]; + reasonCode: null | string; + } + + export interface QqbotMessagePublishTargetView { + enabled: boolean; + id: string; + targetId: string; + targetName: null | string; + targetType: QqbotMessagePushTargetType; + } + + export interface QqbotMessagePublishBindingView { + available: boolean; + createTime: string; + enabled: boolean; + id: string; + invalidReasonCode: null | string; + sourceKey: string; + sourceName: string; + subscriptionId: string; + subscriptionName: string; + targets: QqbotMessagePublishTargetView[]; + templateId: string; + templateName: string; + updateTime: string; + } + + export interface PageResult { + items: T[]; + total: number; + } +} + +/** + * Lists registered system message sources available to the Admin UI. + * @returns Public source definitions without internal adapter details. + */ +export function getMessagePushSources() { + return requestClient.get( + '/qqbot/message-push/sources', + ); +} + +/** + * Loads one public source definition by its stable source key. + * @param sourceKey - String source identity, encoded before becoming a URL segment. + * @returns The requested source definition. + */ +export function getMessagePushSourceDetail(sourceKey: string) { + return requestClient.get( + `/qqbot/message-push/sources/${encodeURIComponent(sourceKey)}`, + ); +} + +/** + * Loads server-evaluated options for the built-in STUN port-change source. + * @returns Eligible and disabled port-forward and DDNS options. + */ +export function getStunMappingPortChangedOptions() { + return requestClient.get( + '/qqbot/message-push/sources/network.stun.mapping-port-changed/options', + ); +} + +/** + * Pages global message subscriptions using independent list filters. + * @param params - Pagination, source, name, and enabled-state filters. + * @returns A strict `{ items, total }` subscription page. + */ +export function getMessageSubscriptionList( + params: QqbotMessagePushApi.MessageSubscriptionListQuery, +) { + return requestClient.get< + QqbotMessagePushApi.PageResult + >('/qqbot/message-push/subscriptions', { params }); +} + +/** + * Creates one source-scoped global message subscription. + * @param data - Complete subscription name, source, configuration, and enabled state. + * @returns The persisted subscription view. + */ +export function createMessageSubscription( + data: QqbotMessagePushApi.MessageSubscriptionInput, +) { + return requestClient.post( + '/qqbot/message-push/subscriptions', + data, + ); +} + +/** + * Replaces one global subscription without coercing its string ID. + * @param id - Stable subscription ID, encoded before becoming a URL segment. + * @param data - Complete replacement subscription input. + * @returns The updated subscription view. + */ +export function updateMessageSubscription( + id: string, + data: QqbotMessagePushApi.MessageSubscriptionInput, +) { + return requestClient.put( + `/qqbot/message-push/subscriptions/${encodeURIComponent(id)}`, + data, + ); +} + +/** + * Changes whether a subscription matches future source events. + * @param id - Stable subscription ID, encoded before becoming a URL segment. + * @param enabled - Requested future enabled state. + * @returns The updated subscription view. + */ +export function setMessageSubscriptionEnabled(id: string, enabled: boolean) { + return requestClient.put( + `/qqbot/message-push/subscriptions/${encodeURIComponent(id)}/enabled`, + { enabled }, + ); +} + +/** + * Soft-deletes one global message subscription. + * @param id - Stable subscription ID, encoded before becoming a URL segment. + * @returns Whether the subscription was deleted. + */ +export function deleteMessageSubscription(id: string) { + return requestClient.delete( + `/qqbot/message-push/subscriptions/${encodeURIComponent(id)}`, + ); +} + +/** + * Pages global message templates using independent list filters. + * @param params - Pagination, source, name, and enabled-state filters. + * @returns A strict `{ items, total }` template page. + */ +export function getMessageTemplateList( + params: QqbotMessagePushApi.MessageTemplateListQuery, +) { + return requestClient.get< + QqbotMessagePushApi.PageResult + >('/qqbot/message-push/templates', { params }); +} + +/** + * Creates one source-scoped global message template. + * @param data - Complete template content, source, metadata, and enabled state. + * @returns The persisted template view. + */ +export function createMessageTemplate( + data: QqbotMessagePushApi.MessageTemplateInput, +) { + return requestClient.post( + '/qqbot/message-push/templates', + data, + ); +} + +/** + * Replaces one global message template without coercing its string ID. + * @param id - Stable template ID, encoded before becoming a URL segment. + * @param data - Complete replacement template input. + * @returns The updated template view. + */ +export function updateMessageTemplate( + id: string, + data: QqbotMessagePushApi.MessageTemplateInput, +) { + return requestClient.put( + `/qqbot/message-push/templates/${encodeURIComponent(id)}`, + data, + ); +} + +/** + * Changes whether a template can be selected for future bindings and events. + * @param id - Stable template ID, encoded before becoming a URL segment. + * @param enabled - Requested future enabled state. + * @returns The updated template view. + */ +export function setMessageTemplateEnabled(id: string, enabled: boolean) { + return requestClient.put( + `/qqbot/message-push/templates/${encodeURIComponent(id)}/enabled`, + { enabled }, + ); +} + +/** + * Soft-deletes one global message template when the backend permits it. + * @param id - Stable template ID, encoded before becoming a URL segment. + * @returns Whether the template was deleted. + */ +export function deleteMessageTemplate(id: string) { + return requestClient.delete( + `/qqbot/message-push/templates/${encodeURIComponent(id)}`, + ); +} + +/** + * Renders a source-scoped message template using server-controlled example data. + * @param data - Template content and source identity only. + * @returns Safe rendered text and the example variables used. + */ +export function previewMessageTemplate( + data: QqbotMessagePushApi.MessageTemplatePreviewInput, +) { + return requestClient.post( + '/qqbot/message-push/templates/preview', + data, + ); +} + +/** + * Lists message-push bindings belonging to exactly one QQBot account. + * @param selfId - QQBot self ID kept as a string and encoded in the URL. + * @returns Account-scoped binding views with their configured targets. + */ +export function getAccountMessagePushBindings(selfId: string) { + return requestClient.get< + QqbotMessagePushApi.QqbotMessagePublishBindingView[] + >(`/qqbot/accounts/${encodeURIComponent(selfId)}/message-push/bindings`); +} + +/** + * Creates one account-scoped binding from a global subscription and template. + * @param selfId - QQBot self ID kept as a string and encoded in the URL. + * @param data - Subscription, template, target, and enabled-state input. + * @returns The persisted account binding view. + */ +export function createAccountMessagePushBinding( + selfId: string, + data: QqbotMessagePushApi.QqbotMessagePublishBindingInput, +) { + return requestClient.post( + `/qqbot/accounts/${encodeURIComponent(selfId)}/message-push/bindings`, + data, + ); +} + +/** + * Replaces one binding for exactly one QQBot account. + * @param selfId - QQBot self ID kept as a string and encoded in the URL. + * @param id - Stable binding ID, encoded before becoming a URL segment. + * @param data - Subscription, template, target, and enabled-state input. + * @returns The updated account binding view. + */ +export function updateAccountMessagePushBinding( + selfId: string, + id: string, + data: QqbotMessagePushApi.QqbotMessagePublishBindingInput, +) { + return requestClient.put( + `/qqbot/accounts/${encodeURIComponent(selfId)}/message-push/bindings/${encodeURIComponent(id)}`, + data, + ); +} + +/** + * Changes whether one account binding creates future delivery work. + * @param selfId - QQBot self ID kept as a string and encoded in the URL. + * @param id - Stable binding ID, encoded before becoming a URL segment. + * @param enabled - Requested future enabled state. + * @returns The updated account binding view. + */ +export function setAccountMessagePushBindingEnabled( + selfId: string, + id: string, + enabled: boolean, +) { + return requestClient.put( + `/qqbot/accounts/${encodeURIComponent(selfId)}/message-push/bindings/${encodeURIComponent(id)}/enabled`, + { enabled }, + ); +} + +/** + * Soft-deletes one binding from exactly one QQBot account. + * @param selfId - QQBot self ID kept as a string and encoded in the URL. + * @param id - Stable binding ID, encoded before becoming a URL segment. + * @returns Whether the account binding was deleted. + */ +export function deleteAccountMessagePushBinding(selfId: string, id: string) { + return requestClient.delete( + `/qqbot/accounts/${encodeURIComponent(selfId)}/message-push/bindings/${encodeURIComponent(id)}`, + ); +} + +/** + * Lists group and private-message target choices for exactly one QQBot account. + * @param selfId - QQBot self ID kept as a string and encoded in the URL. + * @returns Availability state plus currently discoverable string-ID targets. + */ +export function getAccountMessagePushTargets(selfId: string) { + return requestClient.get( + `/qqbot/accounts/${encodeURIComponent(selfId)}/message-push/targets`, + ); +} diff --git a/apps/web-antdv-next/src/router/routes/modules/qqbot.spec.ts b/apps/web-antdv-next/src/router/routes/modules/qqbot.spec.ts new file mode 100644 index 0000000..5cd7baa --- /dev/null +++ b/apps/web-antdv-next/src/router/routes/modules/qqbot.spec.ts @@ -0,0 +1,34 @@ +import { readFileSync } from 'node:fs'; + +import { describe, expect, it } from 'vitest'; + +describe('qqbot routes', () => { + it('adds two flat message-push route children without loading future pages', () => { + const source = readFileSync( + 'apps/web-antdv-next/src/router/routes/modules/qqbot.ts', + 'utf8', + ); + + for (const [name, path, componentPath, title] of [ + [ + 'QqBotMessageSubscription', + '/qqbot/message-subscription', + '#/views/qqbot/message-subscription/list', + '消息订阅', + ], + [ + 'QqBotMessageTemplate', + '/qqbot/message-template', + '#/views/qqbot/message-template/list', + '消息模板', + ], + ] as const) { + expect(source).toMatch( + new RegExp( + String.raw`\{\s*component: \(\) => import\('${componentPath}'\),\s*meta: \{\s*icon: 'lucide:[^']+',\s*title: '${title}',\s*\},\s*name: '${name}',\s*path: '${path}',\s*\}`, + 's', + ), + ); + } + }); +}); diff --git a/apps/web-antdv-next/src/router/routes/modules/qqbot.ts b/apps/web-antdv-next/src/router/routes/modules/qqbot.ts index 0bb2a03..9784754 100644 --- a/apps/web-antdv-next/src/router/routes/modules/qqbot.ts +++ b/apps/web-antdv-next/src/router/routes/modules/qqbot.ts @@ -112,6 +112,24 @@ const routes: RouteRecordRaw[] = [ name: 'QqBotSendLog', path: '/qqbot/sendLog', }, + { + component: () => import('#/views/qqbot/message-subscription/list'), + meta: { + icon: 'lucide:bell-ring', + title: '消息订阅', + }, + name: 'QqBotMessageSubscription', + path: '/qqbot/message-subscription', + }, + { + component: () => import('#/views/qqbot/message-template/list'), + meta: { + icon: 'lucide:message-square-plus', + title: '消息模板', + }, + name: 'QqBotMessageTemplate', + path: '/qqbot/message-template', + }, { component: () => import('#/views/qqbot/permission/list'), meta: {