643 lines
23 KiB
TypeScript
643 lines
23 KiB
TypeScript
import { renderToStaticMarkup } from 'react-dom/server'
|
||
import { describe, expect, it, vi } from 'vitest'
|
||
import { SystemSettingsView, ppSourceConnections, visibleSaveMessage } from './SystemSettings'
|
||
import { AuthContext } from './auth'
|
||
import type { MonitorOverview } from './monitor'
|
||
import type { CatalogSource } from './sourceCatalog'
|
||
import type { UISettings } from './uiSettings'
|
||
import type { SourceConnectionListItem } from './sourceConnections'
|
||
const monitor: MonitorOverview = {
|
||
mode: 'simulation',
|
||
simulated: true,
|
||
observed_at: '2026-09-08T12:00:00Z',
|
||
host: {
|
||
cpu_percent: 42,
|
||
memory_used_bytes: 1,
|
||
memory_total_bytes: 2,
|
||
disk_used_bytes: 1,
|
||
disk_total_bytes: 2,
|
||
gpu_state: 'unknown',
|
||
gpu_detail: '未接入真实GPU监控',
|
||
simulated: true,
|
||
},
|
||
services: [
|
||
{
|
||
key: 'mineru',
|
||
name: 'MinerU',
|
||
state: 'healthy',
|
||
detail: '模拟',
|
||
latency_ms: 1,
|
||
observed_at: '2026-09-08T12:00:00Z',
|
||
simulated: true,
|
||
},
|
||
],
|
||
knowledge_bases: [],
|
||
health: [
|
||
{
|
||
key: 'mineru',
|
||
name: 'MinerU',
|
||
liveness: 'up',
|
||
readiness: 'blocked',
|
||
reason: '服务可达但容量已满(429),暂时不可接单',
|
||
latency_ms: 30,
|
||
observed_at: '2026-09-08T12:00:00Z',
|
||
last_success_at: '2026-09-08T11:59:00Z',
|
||
consecutive_failures: 2,
|
||
configured: true,
|
||
applicable: true,
|
||
},
|
||
],
|
||
}
|
||
const sources: CatalogSource[] = [
|
||
{
|
||
connection_id: 'ding-1',
|
||
source_type: 'dingtalk',
|
||
name: '公司级技术支持知识库',
|
||
root_node_id: 'root-1',
|
||
folder_count: 1079,
|
||
file_count: 3444,
|
||
observed_at: '2026-09-09T09:30:32Z',
|
||
snapshot_only: true,
|
||
snapshot_status: 'ready',
|
||
last_refresh_duration_ms: 0,
|
||
},
|
||
]
|
||
const connections: SourceConnectionListItem[] = [
|
||
{
|
||
id: 'conn-ding',
|
||
source_type: 'dingtalk',
|
||
name: '公司级技术支持知识库',
|
||
enabled: true,
|
||
schedule_configured: true,
|
||
health_status: 'healthy',
|
||
item_count: 3444,
|
||
updated_at: '2026-09-09T09:30:32Z',
|
||
next_refresh_at: '2026-09-12T10:00:00Z',
|
||
},
|
||
{
|
||
id: 'conn-man',
|
||
source_type: 'manual',
|
||
name: '本地手工验收',
|
||
enabled: false,
|
||
schedule_configured: false,
|
||
health_status: 'unknown',
|
||
item_count: 5,
|
||
updated_at: '2026-09-09T09:30:32Z',
|
||
next_refresh_at: null,
|
||
},
|
||
]
|
||
const settings: UISettings = {
|
||
id: 'settings-default',
|
||
state: 'active',
|
||
document_refresh_seconds: 15,
|
||
resource_refresh_seconds: 30,
|
||
stalled_after_seconds: 1800,
|
||
finalizing_after_seconds: 7200,
|
||
ingestion_concurrency: 2,
|
||
test_weknora_base_url: '',
|
||
production_weknora_base_url: '',
|
||
production_tenant_id: '',
|
||
production_knowledge_base_id: '',
|
||
updated_by: 'system:migration',
|
||
created_at: '2026-09-10T08:00:00Z',
|
||
activated_at: '2026-09-10T08:00:00Z',
|
||
retired_at: null,
|
||
error_code: '',
|
||
}
|
||
const authValue = (role: 'admin' | 'engineer') => ({
|
||
identity: { id: `${role}-1`, username: role, display_name: role, role, must_change_password: false },
|
||
ready: true,
|
||
login: async () => null,
|
||
logout: async () => {},
|
||
changePassword: async () => null,
|
||
signedOut: 0,
|
||
notifySignedOut: () => {},
|
||
})
|
||
describe('SystemSettingsView service configuration', () => {
|
||
it('lets an administrator edit exactly the four service fields in the web console', () => {
|
||
const configured: UISettings = {
|
||
...settings,
|
||
test_weknora_base_url: 'https://weknora.test.example',
|
||
production_weknora_base_url: 'https://weknora.prod.example',
|
||
production_tenant_id: '10000',
|
||
production_knowledge_base_id: 'kb-abc123',
|
||
}
|
||
const view = (role: 'admin' | 'engineer') =>
|
||
renderToStaticMarkup(
|
||
<AuthContext.Provider value={authValue(role)}>
|
||
<SystemSettingsView
|
||
sources={sources}
|
||
connections={connections}
|
||
monitor={monitor}
|
||
settings={configured}
|
||
productionEnabled={false}
|
||
routingCapabilities={{ internal_enabled: true, external_enabled: true }}
|
||
loading={false}
|
||
saving={false}
|
||
error={null}
|
||
saveMessage={null}
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
initialSection="services"
|
||
/>
|
||
</AuthContext.Provider>
|
||
)
|
||
const adminHtml = view('admin')
|
||
for (const text of [
|
||
'测试地址',
|
||
'生产地址',
|
||
'生产 ID',
|
||
'知识库 ID',
|
||
'保存并启用服务配置',
|
||
'https://weknora.test.example',
|
||
'https://weknora.prod.example',
|
||
'10000',
|
||
'kb-abc123',
|
||
'四项留空即表示未配置',
|
||
])
|
||
expect(adminHtml).toContain(text)
|
||
expect(adminHtml).not.toContain('仅管理员可修改')
|
||
// 界面只剩这四项:依赖探活矩阵与对内/对外绑定下拉不再出现在服务配置页。
|
||
for (const removed of ['MinerU', 'Embedding', 'Redis / MQ', 'PostgreSQL', '测试知识库映射'])
|
||
expect(adminHtml).not.toContain(removed)
|
||
const engineerHtml = view('engineer')
|
||
expect(engineerHtml).toContain('仅管理员可修改')
|
||
expect(engineerHtml).not.toContain('保存并启用服务配置')
|
||
})
|
||
})
|
||
describe('SystemSettingsView', () => {
|
||
it('separates document-engineer settings from locked administrator and production controls', () => {
|
||
const html = renderToStaticMarkup(
|
||
<SystemSettingsView
|
||
sources={sources}
|
||
connections={connections}
|
||
monitor={monitor}
|
||
settings={settings}
|
||
productionEnabled={false}
|
||
routingCapabilities={{ internal_enabled: true, external_enabled: true }}
|
||
loading={false}
|
||
saving={false}
|
||
error={null}
|
||
saveMessage="刷新与告警参数已验证并启用:文档15秒、资源30秒、停滞30分钟、finalizing 2小时"
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
/>
|
||
)
|
||
for (const text of ['系统设置', '数据源配置', '服务配置', '存储与备份', '刷新策略']) expect(html).toContain(text)
|
||
// 主人 2026-10-26 要求去掉的噪声:页头副标题、刷新状态、权限条、活动版本横幅。
|
||
for (const removed of [
|
||
'查看内容源、服务与备份状态',
|
||
'刷新状态',
|
||
'文档工程师可调整',
|
||
'管理员设置已锁定',
|
||
'设置服务已启用',
|
||
])
|
||
expect(html).not.toContain(removed)
|
||
// 需求(2026-10-14 收敛):数据源配置只剩两个入口 —— dws 导入(按当前钉钉账号发现)
|
||
// 与 PP 导入;AList/静态钉钉知识库等来源类型不再出现在这里(也没有折叠开关兜着)。
|
||
expect(html).toContain('公司级技术支持知识库')
|
||
expect(html).not.toContain('dws 共享授权')
|
||
for (const hidden of ['专用浏览器扫码会话', '显示 AList/钉钉知识库来源']) expect(html).not.toContain(hidden)
|
||
// 统一入口后不再按类型分标题:PP 与钉钉共用同一张清单与同一个添加向导。
|
||
expect(html).not.toContain('PP 导入')
|
||
// 需求(2026-10-10):数据源配置不再做文件格式筛选(格式由目录级标注决定)。
|
||
for (const removed of ['文件格式筛选', '未勾选的一律不取件', '保存保留格式', '恢复继承上级规则'])
|
||
expect(html).not.toContain(removed)
|
||
// 需求(2026-10-10):新增入口只剩「可加入的来源」勾选,粘贴链接的按钮已下线。
|
||
for (const removed of ['+ 新增钉钉知识库', '新增钉钉知识库']) expect(html).not.toContain(removed)
|
||
const servicesHtml = renderToStaticMarkup(
|
||
<SystemSettingsView
|
||
sources={sources}
|
||
connections={connections}
|
||
monitor={monitor}
|
||
settings={settings}
|
||
productionEnabled={false}
|
||
routingCapabilities={{ internal_enabled: true, external_enabled: true }}
|
||
loading={false}
|
||
saving={false}
|
||
error={null}
|
||
saveMessage={null}
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
initialSection="services"
|
||
/>
|
||
)
|
||
for (const text of ['测试 WeKnora', '生产 WeKnora', '测试地址', '生产地址', '生产 ID', '知识库 ID'])
|
||
expect(servicesHtml).toContain(text)
|
||
const storageHtml = renderToStaticMarkup(
|
||
<SystemSettingsView
|
||
sources={sources}
|
||
connections={connections}
|
||
monitor={monitor}
|
||
settings={settings}
|
||
productionEnabled={false}
|
||
routingCapabilities={{ internal_enabled: true, external_enabled: true }}
|
||
loading={false}
|
||
saving={false}
|
||
error={null}
|
||
saveMessage={null}
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
initialSection="storage"
|
||
/>
|
||
)
|
||
// 存储与备份分区已换成真实功能(整库逻辑备份的定时计划),静态占位文案不再存在。
|
||
expect(storageHtml).toContain('存储与备份')
|
||
expect(storageHtml).toContain('正在读取备份计划…')
|
||
expect(storageHtml).not.toContain('仅保存凭据引用,不可回读')
|
||
const refreshHtml = renderToStaticMarkup(
|
||
<SystemSettingsView
|
||
sources={sources}
|
||
connections={connections}
|
||
monitor={monitor}
|
||
settings={settings}
|
||
productionEnabled={false}
|
||
routingCapabilities={{ internal_enabled: true, external_enabled: true }}
|
||
loading={false}
|
||
saving={false}
|
||
error={null}
|
||
saveMessage="刷新与告警参数已验证并启用:文档15秒、资源30秒、停滞30分钟、finalizing 2小时"
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
initialSection="refresh"
|
||
/>
|
||
)
|
||
for (const text of [
|
||
'刷新与告警参数已验证并启用',
|
||
'停滞30分钟',
|
||
'finalizing 2小时',
|
||
'文档状态刷新间隔',
|
||
'15秒',
|
||
'解析停滞判定',
|
||
'30分钟',
|
||
'保存并应用刷新与告警参数',
|
||
])
|
||
expect(refreshHtml).toContain(text)
|
||
expect(visibleSaveMessage('已保存', true)).toBeNull()
|
||
expect(visibleSaveMessage('已保存', false)).toBe('已保存')
|
||
expect(html).not.toContain('本地模拟')
|
||
expect(html).not.toContain('保存模拟设置')
|
||
for (const forbidden of ['DATABASE_URL', 'RELEASE_CONFIG_FILE', 'API Key明文', 'secret_ref'])
|
||
expect(html).not.toContain(forbidden)
|
||
// 两个环境卡仍由真实探测驱动(依赖矩阵搬到总览页,本页不再渲染)。
|
||
expect(servicesHtml).toContain('解析队列 对内 已配置 · 对外 已配置')
|
||
expect(servicesHtml).not.toContain('PostgreSQL')
|
||
expect(servicesHtml).toContain('端点探测:暂无真实探测结果')
|
||
// V1: left-nav section controls, per-source schedule editing, danger modal & history affordances.
|
||
expect(html).toContain('settings-nav')
|
||
for (const kept of ['更多', '自动刷新中']) expect(html).toContain(kept)
|
||
for (const hidden of ['查看修订历史', '保存并启用调度', '停用来源']) expect(html).not.toContain(hidden)
|
||
expect(html).not.toContain('本页只列 PP 来源')
|
||
expect(html).not.toContain('本地手工导入来源不支持定时刷新')
|
||
// dead buttons removed
|
||
expect(html).not.toContain('添加内容源')
|
||
expect(html).not.toContain('管理员编辑')
|
||
})
|
||
it('lets an administrator bind the internal/external import knowledge bases from 系统设置', () => {
|
||
// 组件一直在(RoutingBindingsSection),但从未挂到任何页面 —— 导入目标库只能靠
|
||
// 部署 env 钉死,换库必须重建容器。
|
||
const view = (section: 'sources' | 'bindings' | 'production-bindings') =>
|
||
renderToStaticMarkup(
|
||
<AuthContext.Provider value={authValue('admin')}>
|
||
<SystemSettingsView
|
||
sources={sources}
|
||
connections={connections}
|
||
monitor={monitor}
|
||
settings={settings}
|
||
productionEnabled={false}
|
||
routingCapabilities={{ internal_enabled: true, external_enabled: true }}
|
||
loading={false}
|
||
saving={false}
|
||
error={null}
|
||
saveMessage={null}
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
initialSection={section}
|
||
/>
|
||
</AuthContext.Provider>
|
||
)
|
||
expect(view('sources')).toContain('测试知识库绑定')
|
||
expect(view('sources')).toContain('生产知识库绑定')
|
||
const bound = view('bindings')
|
||
expect(bound).toContain('id="settings-routing-bindings"')
|
||
expect(bound).toContain('分发到对应的测试 WeKnora 知识库')
|
||
// 未读到绑定数据时(SSR 不跑 effect)面板也要如实存在,而不是空白。
|
||
expect(bound).toContain('读取中…')
|
||
// 发布目标(源库 → 目标库)是独立分区,与测试端收件库分开。
|
||
const target = view('production-bindings')
|
||
expect(target).toContain('id="settings-production-bindings"')
|
||
expect(target).toContain('新发布的对内 / 对外内容按这里的绑定写入生产 WeKnora 的对应知识库')
|
||
})
|
||
it('does not show healthy simulation when the settings data failed to load', () => {
|
||
const html = renderToStaticMarkup(
|
||
<SystemSettingsView
|
||
sources={[]}
|
||
connections={[]}
|
||
monitor={null}
|
||
settings={null}
|
||
productionEnabled={false}
|
||
routingCapabilities={{ internal_enabled: false, external_enabled: false }}
|
||
loading={false}
|
||
saving={false}
|
||
error="加载失败"
|
||
saveMessage={null}
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
/>
|
||
)
|
||
expect(html).toContain('加载失败')
|
||
expect(html).not.toContain('模拟运行中')
|
||
})
|
||
it('renders the WeKnora cards and production channel from live probe and the real production gate', () => {
|
||
const probed: MonitorOverview = {
|
||
...monitor,
|
||
health: [
|
||
{
|
||
key: 'weknora',
|
||
name: 'WeKnora API',
|
||
liveness: 'up',
|
||
readiness: 'ready',
|
||
reason: '',
|
||
latency_ms: 86,
|
||
observed_at: '2026-09-08T12:00:00Z',
|
||
last_success_at: '2026-09-08T11:59:00Z',
|
||
consecutive_failures: 0,
|
||
configured: true,
|
||
applicable: true,
|
||
},
|
||
{
|
||
key: 'weknora-production',
|
||
name: '生产 WeKnora',
|
||
liveness: 'up',
|
||
readiness: 'ready',
|
||
reason: '',
|
||
latency_ms: 41,
|
||
observed_at: '2026-09-08T12:00:00Z',
|
||
last_success_at: '2026-09-08T11:59:00Z',
|
||
consecutive_failures: 0,
|
||
configured: true,
|
||
applicable: true,
|
||
},
|
||
{
|
||
key: 'embedding',
|
||
name: 'Embedding',
|
||
liveness: 'up',
|
||
readiness: 'not_applicable',
|
||
reason: '未部署:本平台不使用该依赖',
|
||
latency_ms: 0,
|
||
observed_at: '2026-09-08T12:00:00Z',
|
||
last_success_at: null,
|
||
consecutive_failures: 0,
|
||
configured: false,
|
||
applicable: false,
|
||
},
|
||
],
|
||
}
|
||
const view = (enabled: boolean, verified: boolean) =>
|
||
renderToStaticMarkup(
|
||
<SystemSettingsView
|
||
sources={sources}
|
||
connections={connections}
|
||
monitor={probed}
|
||
settings={settings}
|
||
productionEnabled={enabled}
|
||
productionBackupRestoreVerified={verified}
|
||
routingCapabilities={{ internal_enabled: true, external_enabled: true }}
|
||
loading={false}
|
||
saving={false}
|
||
error={null}
|
||
saveMessage={null}
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
initialSection="services"
|
||
/>
|
||
)
|
||
const on = view(true, true)
|
||
expect(on).toContain('测试 WeKnora')
|
||
expect(on).toContain('可达 · 可接单')
|
||
expect(on).toContain('延迟 86ms')
|
||
expect(on).toContain('已启用 · 可提交生产请求')
|
||
expect(on).toContain('生产写入:开启')
|
||
expect(on).toContain('发布目标与凭据:管理员受限配置')
|
||
const off = view(false, false)
|
||
expect(off).toContain('未启用')
|
||
expect(off).toContain('生产写入:关闭')
|
||
// 卡片状态由真实探测/门禁驱动,写死的“已连接 / 未连接 / 生产映射:未配置”必须消失。
|
||
for (const removed of ['本地真实连接', '生产映射:未配置']) expect(off).not.toContain(removed)
|
||
// 生产端点可达性来自真实探活,且只说真话:探活不等于验证过接单能力。
|
||
expect(on).toContain('端点探测:端点可达 · 未做接单验证')
|
||
const blocked = renderToStaticMarkup(
|
||
<SystemSettingsView
|
||
sources={sources}
|
||
connections={connections}
|
||
monitor={monitor}
|
||
settings={settings}
|
||
productionEnabled={true}
|
||
productionBackupRestoreVerified={false}
|
||
routingCapabilities={{ internal_enabled: false, external_enabled: false }}
|
||
loading={false}
|
||
saving={false}
|
||
error={null}
|
||
saveMessage={null}
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
initialSection="services"
|
||
/>
|
||
)
|
||
expect(blocked).toContain('已启用 · 被阻断')
|
||
// 未配置生产端点时必须显式说「未配置」,不得靠文案含糊过去。
|
||
expect(blocked).toContain('端点探测:暂无真实探测结果')
|
||
const unconfigured = renderToStaticMarkup(
|
||
<SystemSettingsView
|
||
sources={sources}
|
||
connections={connections}
|
||
monitor={{
|
||
...monitor,
|
||
health: [
|
||
{
|
||
key: 'weknora-production',
|
||
name: '生产 WeKnora',
|
||
liveness: 'unknown',
|
||
readiness: 'not_configured',
|
||
reason: '已部署但未配置探测参数',
|
||
latency_ms: 0,
|
||
observed_at: '2026-09-08T12:00:00Z',
|
||
last_success_at: null,
|
||
consecutive_failures: 0,
|
||
configured: false,
|
||
applicable: false,
|
||
},
|
||
],
|
||
}}
|
||
settings={settings}
|
||
productionEnabled={false}
|
||
routingCapabilities={{ internal_enabled: false, external_enabled: false }}
|
||
loading={false}
|
||
saving={false}
|
||
error={null}
|
||
saveMessage={null}
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
initialSection="services"
|
||
/>
|
||
)
|
||
expect(unconfigured).toContain('端点探测:未配置')
|
||
expect(unconfigured).not.toContain('端点可达')
|
||
})
|
||
})
|
||
describe('SystemSettingsView 数据源配置的默认清单', () => {
|
||
const withPP: SourceConnectionListItem[] = [
|
||
{
|
||
id: 'conn-pp',
|
||
source_type: 'pp',
|
||
name: 'PP资料下载中心',
|
||
enabled: true,
|
||
schedule_configured: true,
|
||
health_status: 'healthy',
|
||
item_count: 613,
|
||
updated_at: '2026-09-09T09:30:32Z',
|
||
next_refresh_at: null,
|
||
},
|
||
{
|
||
id: 'conn-alist',
|
||
source_type: 'alist',
|
||
name: '国内工业',
|
||
enabled: false,
|
||
schedule_configured: false,
|
||
health_status: 'unknown',
|
||
item_count: 3027,
|
||
updated_at: '2026-09-09T09:30:32Z',
|
||
next_refresh_at: null,
|
||
},
|
||
{
|
||
id: 'conn-ding2',
|
||
source_type: 'dingtalk',
|
||
name: 'ATOM知识库',
|
||
enabled: false,
|
||
schedule_configured: false,
|
||
health_status: 'unknown',
|
||
item_count: 109,
|
||
updated_at: '2026-09-09T09:30:32Z',
|
||
next_refresh_at: null,
|
||
},
|
||
]
|
||
const view = (role: 'admin' | 'engineer' = 'admin') =>
|
||
renderToStaticMarkup(
|
||
<AuthContext.Provider value={authValue(role)}>
|
||
<SystemSettingsView
|
||
sources={[]}
|
||
connections={withPP}
|
||
monitor={monitor}
|
||
settings={settings}
|
||
productionEnabled={false}
|
||
routingCapabilities={{ internal_enabled: true, external_enabled: true }}
|
||
loading={false}
|
||
saving={false}
|
||
error={null}
|
||
saveMessage={null}
|
||
onSave={vi.fn()}
|
||
onSourcesChanged={vi.fn()}
|
||
onRefresh={vi.fn()}
|
||
/>
|
||
</AuthContext.Provider>
|
||
)
|
||
it('统一来源清单显示已接入的 PP 和钉钉来源,不依赖目录快照', () => {
|
||
const html = view()
|
||
expect(html).toContain('PP资料下载中心')
|
||
expect(html).toContain('PP 资料下载中心')
|
||
for (const name of ['国内工业', 'ATOM知识库']) expect(html).toContain(name)
|
||
expect(html).not.toContain('显示 AList')
|
||
// 统一入口:一个「+ 添加数据源」向导,不再有按类型分家的 dws 导入面板标题。
|
||
expect(html).toContain('+ 添加数据源')
|
||
expect(html).not.toContain('PP 导入')
|
||
expect(html).not.toContain('去掉来源')
|
||
for (const removed of ['端点与凭据', '认证方式', '查看修订历史', '保存并启用调度', '停用来源', '自动刷新周期'])
|
||
expect(html).not.toContain(removed)
|
||
expect(html).toContain('更多')
|
||
expect(html).toContain('aria-expanded="false"')
|
||
expect(html).toContain('仅手动刷新')
|
||
expect(html).toContain('数据源设置审计')
|
||
expect((html.match(/DWS 凭证 ·/g) ?? []).length).toBe(1)
|
||
for (const removed of ['+ 新增钉钉知识库', '文件格式筛选']) expect(html).not.toContain(removed)
|
||
// 文档工程师看不到添加向导与「去掉来源」:两者都是管理员动作。
|
||
const engineer = view('engineer')
|
||
expect(engineer).not.toContain('+ 添加数据源')
|
||
expect(engineer).not.toContain('去掉来源')
|
||
// PP 与钉钉共用同一张清单:工程师看得到卡,只是没有添加与归档。
|
||
expect(engineer).toContain('PP资料下载中心')
|
||
})
|
||
})
|
||
describe('ppSourceConnections(数据源配置的清单规则)', () => {
|
||
const rows: SourceConnectionListItem[] = [
|
||
{
|
||
id: 'a',
|
||
source_type: 'pp',
|
||
name: 'PP资料下载中心',
|
||
enabled: true,
|
||
schedule_configured: true,
|
||
health_status: 'healthy',
|
||
item_count: 1,
|
||
updated_at: '2026-09-09T09:30:32Z',
|
||
next_refresh_at: null,
|
||
},
|
||
{
|
||
id: 'b',
|
||
source_type: 'alist',
|
||
name: '国内工业',
|
||
enabled: false,
|
||
schedule_configured: false,
|
||
health_status: 'unknown',
|
||
item_count: 2,
|
||
updated_at: '2026-09-09T09:30:32Z',
|
||
next_refresh_at: null,
|
||
},
|
||
{
|
||
id: 'c',
|
||
source_type: 'dingtalk',
|
||
name: 'ATOM知识库',
|
||
enabled: false,
|
||
schedule_configured: false,
|
||
health_status: 'unknown',
|
||
item_count: 3,
|
||
updated_at: '2026-09-09T09:30:32Z',
|
||
next_refresh_at: null,
|
||
},
|
||
{
|
||
id: 'd',
|
||
source_type: 'manual',
|
||
name: '本地手工验收',
|
||
enabled: false,
|
||
schedule_configured: false,
|
||
health_status: 'unknown',
|
||
item_count: 4,
|
||
updated_at: '2026-09-09T09:30:32Z',
|
||
next_refresh_at: null,
|
||
},
|
||
{
|
||
id: 'e',
|
||
source_type: 'pp',
|
||
name: 'PP legacy',
|
||
enabled: false,
|
||
schedule_configured: false,
|
||
health_status: 'unknown',
|
||
item_count: 5,
|
||
updated_at: '2026-09-09T09:30:32Z',
|
||
next_refresh_at: null,
|
||
},
|
||
]
|
||
it('只放行 PP 来源,顺序不变,其余类型一条都不放行', () => {
|
||
expect(ppSourceConnections(rows).map((item) => item.id)).toEqual(['a', 'e'])
|
||
expect(ppSourceConnections([])).toEqual([])
|
||
})
|
||
})
|