|
|
@@ -0,0 +1,185 @@
|
|
|
+import { ParseApi } from './parse-api';
|
|
|
+
|
|
|
+/**
|
|
|
+ * 字段归一单测(2026-10-20 新增兼容层)
|
|
|
+ *
|
|
|
+ * 🔴 背景:实测 `agent-node-lj-test` 容器上报的字段名/结构与平台契约不符:
|
|
|
+ * · `channels` 上报数组 `["wecom"]`(契约要求对象)→ 导致 matrixReady=false
|
|
|
+ * · `host` 上报 `cpuLoad1`(契约要求 `cpuPercent`)→ CPU 不显示
|
|
|
+ * · `heartbeatAt` 缺失(容器用 `host.probedAt`)
|
|
|
+ *
|
|
|
+ * 本测试覆盖 `mapAgent()` 内的三个归一函数(经 fetchAgents 走通)。
|
|
|
+ */
|
|
|
+describe('parse-api 字段归一(兼容非标准容器)', () => {
|
|
|
+ let api: ParseApi;
|
|
|
+ let calls: { url: string; init: RequestInit }[] = [];
|
|
|
+
|
|
|
+ function stubFetch(body: unknown): void {
|
|
|
+ calls = [];
|
|
|
+ spyOn(window, 'fetch').and.callFake((input: RequestInfo | URL, init?: RequestInit) => {
|
|
|
+ calls.push({ url: String(input), init: init ?? {} });
|
|
|
+ return Promise.resolve({
|
|
|
+ ok: true,
|
|
|
+ status: 200,
|
|
|
+ json: () => Promise.resolve(body),
|
|
|
+ } as unknown as Response);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ api = new ParseApi(() => 'r:t', () => 'all');
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('channels 数组 → 对象', () => {
|
|
|
+ it('["wecom"] → {wecom:true}(matrix 未点亮)', async () => {
|
|
|
+ stubFetch({ code: 200, agents: [{ agentId: 'a', channels: '["wecom"]' }] });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.channels?.wecom).toBe(true);
|
|
|
+ expect(a.channels?.matrix).toBeFalsy();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('["matrix","wecom"] → 两者都点亮', async () => {
|
|
|
+ stubFetch({ code: 200, agents: [{ agentId: 'a', channels: '["matrix","wecom"]' }] });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.channels?.matrix).toBe(true);
|
|
|
+ expect(a.channels?.wecom).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('🔴 数组含 matrix → matrixReady=true(修复"未接入"误判)', async () => {
|
|
|
+ stubFetch({ code: 200, agents: [{ agentId: 'a', channels: '["matrix"]' }] });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.matrixReady).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('对象形态(契约标准)原样通过', async () => {
|
|
|
+ stubFetch({
|
|
|
+ code: 200,
|
|
|
+ agents: [
|
|
|
+ {
|
|
|
+ agentId: 'a',
|
|
|
+ channels: '{"matrix":true,"homeserver":"https://matrix.fmode.cn","matrixConfig":{"userId":"@x:fmode.cn"}}',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.channels?.matrix).toBe(true);
|
|
|
+ expect(a.channels?.matrixConfig?.userId).toBe('@x:fmode.cn');
|
|
|
+ expect(a.matrixReady).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('空数组 → undefined(不是"已接入")', async () => {
|
|
|
+ stubFetch({ code: 200, agents: [{ agentId: 'a', channels: '[]' }] });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.channels).toBeUndefined();
|
|
|
+ expect(a.matrixReady).toBe(false);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('host 归一:cpuLoad1 → cpuPercent', () => {
|
|
|
+ it('🔴 cpuLoad1=8, cpuCores=8 → cpuPercent=100', async () => {
|
|
|
+ stubFetch({
|
|
|
+ code: 200,
|
|
|
+ agents: [{ agentId: 'a', host: '{"cpuLoad1":8,"cpuCores":8,"memPercent":50,"diskPercent":30}' }],
|
|
|
+ });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.host?.cpuPercent).toBe(100);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('🔴 核数未知时不算 cpuPercent(不编假数字)', async () => {
|
|
|
+ // 实测契约口径是 cpuLoad1 / 核数;核数缺失时宁可"未上报",也不假定核数
|
|
|
+ stubFetch({
|
|
|
+ code: 200,
|
|
|
+ agents: [{ agentId: 'a', host: '{"cpuLoad1":8,"memPercent":50,"diskPercent":30}' }],
|
|
|
+ });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.host?.cpuPercent).toBeUndefined();
|
|
|
+ expect(a.host?.memPercent).toBe(50); // 其余指标仍保留
|
|
|
+ });
|
|
|
+
|
|
|
+ it('cpuLoad1=9.2, cpuCores=56 → cpuPercent≈16.4', async () => {
|
|
|
+ stubFetch({
|
|
|
+ code: 200,
|
|
|
+ agents: [
|
|
|
+ { agentId: 'a', host: '{"cpuLoad1":9.2,"cpuCores":56,"memPercent":55.6,"diskPercent":33.3}' },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.host?.cpuPercent).toBeCloseTo(16.4, 1);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('契约标准的 cpuPercent 优先(不被覆盖)', async () => {
|
|
|
+ stubFetch({
|
|
|
+ code: 200,
|
|
|
+ agents: [{ agentId: 'a', host: '{"cpuPercent":42,"cpuLoad1":99,"memPercent":10}' }],
|
|
|
+ });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.host?.cpuPercent).toBe(42);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('越界负载被夹到 0~100', async () => {
|
|
|
+ stubFetch({ code: 200, agents: [{ agentId: 'a', host: '{"cpuLoad1":999,"cpuCores":2}' }] });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.host?.cpuPercent).toBe(100);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('三项全无 → undefined(UI 显示"未上报"而非 0%)', async () => {
|
|
|
+ stubFetch({ code: 200, agents: [{ agentId: 'a', host: '{"hostname":"x"}' }] });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.host).toBeUndefined();
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('heartbeatAt 归一', () => {
|
|
|
+ it('契约字段 heartbeatAt 优先', async () => {
|
|
|
+ stubFetch({
|
|
|
+ code: 200,
|
|
|
+ agents: [{ agentId: 'a', heartbeatAt: '2026-10-20T10:00:00Z', host: '{"probedAt":"2026-01-01T00:00:00Z"}' }],
|
|
|
+ });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.heartbeatAt).toBe('2026-10-20T10:00:00Z');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('缺失时回落 host.probedAt(容器实际用的键)', async () => {
|
|
|
+ stubFetch({
|
|
|
+ code: 200,
|
|
|
+ agents: [{ agentId: 'a', host: '{"probedAt":"2026-10-20T10:34:07+08:00"}' }],
|
|
|
+ });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.heartbeatAt).toBe('2026-10-20T10:34:07+08:00');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('再缺失时回落 updatedAtClient.iso', async () => {
|
|
|
+ stubFetch({
|
|
|
+ code: 200,
|
|
|
+ agents: [{ agentId: 'a', updatedAtClient: { iso: '2026-10-20T02:33:55.213Z' } }],
|
|
|
+ });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.heartbeatAt).toBe('2026-10-20T02:33:55.213Z');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('综合:非标准容器的完整映射', () => {
|
|
|
+ it('数组 channels + cpuLoad1 + probedAt → 全部归一正确', async () => {
|
|
|
+ stubFetch({
|
|
|
+ code: 200,
|
|
|
+ agents: [
|
|
|
+ {
|
|
|
+ agentId: 'agent-node-lj-test',
|
|
|
+ botName: 'lj-test',
|
|
|
+ status: 'running',
|
|
|
+ currentTask: 'periodic status reporter',
|
|
|
+ channels: '["matrix","wecom"]',
|
|
|
+ host: '{"hostname":"e14260fdce46","cpuLoad1":9.2,"cpuCores":56,"memPercent":55.6,"diskPercent":33.3,"probedAt":"2026-10-20T10:34:07+08:00"}',
|
|
|
+ balance: '{"source":"none"}',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ const [a] = await api.fetchAgents();
|
|
|
+ expect(a.id).toBe('agent-node-lj-test');
|
|
|
+ expect(a.matrixReady).toBe(true); // ← 修复前是 false
|
|
|
+ expect(a.host?.cpuPercent).toBeCloseTo(16.4, 1); // ← 修复前是 undefined
|
|
|
+ expect(a.host?.memPercent).toBe(55.6);
|
|
|
+ expect(a.heartbeatAt).toBe('2026-10-20T10:34:07+08:00'); // ← 修复前是 undefined
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|