mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:28:38 +00:00
test: dedupe activity and diagnostic coverage
This commit is contained in:
72
src/infra/channel-activity.test.ts
Normal file
72
src/infra/channel-activity.test.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import {
|
||||||
|
getChannelActivity,
|
||||||
|
recordChannelActivity,
|
||||||
|
resetChannelActivityForTest,
|
||||||
|
} from "./channel-activity.js";
|
||||||
|
|
||||||
|
describe("channel activity", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
resetChannelActivityForTest();
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(new Date("2026-01-08T00:00:00Z"));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the default account for blank inputs and falls back to null timestamps", () => {
|
||||||
|
expect(getChannelActivity({ channel: "telegram" })).toEqual({
|
||||||
|
inboundAt: null,
|
||||||
|
outboundAt: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
recordChannelActivity({
|
||||||
|
channel: "telegram",
|
||||||
|
accountId: " ",
|
||||||
|
direction: "inbound",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getChannelActivity({ channel: "telegram", accountId: null })).toEqual({
|
||||||
|
inboundAt: 1767830400000,
|
||||||
|
outboundAt: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps inbound and outbound timestamps independent and trims account ids", () => {
|
||||||
|
recordChannelActivity({
|
||||||
|
channel: "whatsapp",
|
||||||
|
accountId: " team-a ",
|
||||||
|
direction: "inbound",
|
||||||
|
at: 10,
|
||||||
|
});
|
||||||
|
recordChannelActivity({
|
||||||
|
channel: "whatsapp",
|
||||||
|
accountId: "team-a",
|
||||||
|
direction: "outbound",
|
||||||
|
at: 20,
|
||||||
|
});
|
||||||
|
recordChannelActivity({
|
||||||
|
channel: "whatsapp",
|
||||||
|
accountId: "team-a",
|
||||||
|
direction: "inbound",
|
||||||
|
at: 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getChannelActivity({ channel: "whatsapp", accountId: " team-a " })).toEqual({
|
||||||
|
inboundAt: 30,
|
||||||
|
outboundAt: 20,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reset clears previously recorded activity", () => {
|
||||||
|
recordChannelActivity({ channel: "line", direction: "outbound", at: 7 });
|
||||||
|
resetChannelActivityForTest();
|
||||||
|
|
||||||
|
expect(getChannelActivity({ channel: "line" })).toEqual({
|
||||||
|
inboundAt: null,
|
||||||
|
outboundAt: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
49
src/infra/channels-status-issues.test.ts
Normal file
49
src/infra/channels-status-issues.test.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const listChannelPluginsMock = vi.hoisted(() => vi.fn());
|
||||||
|
|
||||||
|
vi.mock("../channels/plugins/index.js", () => ({
|
||||||
|
listChannelPlugins: () => listChannelPluginsMock(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { collectChannelStatusIssues } from "./channels-status-issues.js";
|
||||||
|
|
||||||
|
describe("collectChannelStatusIssues", () => {
|
||||||
|
it("returns no issues when payload accounts are missing or not arrays", () => {
|
||||||
|
const collectTelegramIssues = vi.fn(() => [{ code: "telegram" }]);
|
||||||
|
listChannelPluginsMock.mockReturnValue([
|
||||||
|
{ id: "telegram", status: { collectStatusIssues: collectTelegramIssues } },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(collectChannelStatusIssues({})).toEqual([]);
|
||||||
|
expect(collectChannelStatusIssues({ channelAccounts: { telegram: { bad: true } } })).toEqual(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
expect(collectTelegramIssues).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips plugins without collectors and concatenates collector output in plugin order", () => {
|
||||||
|
const collectTelegramIssues = vi.fn(() => [{ code: "telegram.down" }]);
|
||||||
|
const collectSlackIssues = vi.fn(() => [{ code: "slack.warn" }, { code: "slack.auth" }]);
|
||||||
|
const telegramAccounts = [{ id: "tg-1" }];
|
||||||
|
const slackAccounts = [{ id: "sl-1" }];
|
||||||
|
listChannelPluginsMock.mockReturnValueOnce([
|
||||||
|
{ id: "discord" },
|
||||||
|
{ id: "telegram", status: { collectStatusIssues: collectTelegramIssues } },
|
||||||
|
{ id: "slack", status: { collectStatusIssues: collectSlackIssues } },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
collectChannelStatusIssues({
|
||||||
|
channelAccounts: {
|
||||||
|
discord: [{ id: "dc-1" }],
|
||||||
|
telegram: telegramAccounts,
|
||||||
|
slack: slackAccounts,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([{ code: "telegram.down" }, { code: "slack.warn" }, { code: "slack.auth" }]);
|
||||||
|
|
||||||
|
expect(collectTelegramIssues).toHaveBeenCalledWith(telegramAccounts);
|
||||||
|
expect(collectSlackIssues).toHaveBeenCalledWith(slackAccounts);
|
||||||
|
});
|
||||||
|
});
|
||||||
65
src/infra/diagnostic-flags.test.ts
Normal file
65
src/infra/diagnostic-flags.test.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
|
import {
|
||||||
|
isDiagnosticFlagEnabled,
|
||||||
|
matchesDiagnosticFlag,
|
||||||
|
resolveDiagnosticFlags,
|
||||||
|
} from "./diagnostic-flags.js";
|
||||||
|
|
||||||
|
describe("resolveDiagnosticFlags", () => {
|
||||||
|
it("normalizes and dedupes config and env flags", () => {
|
||||||
|
const cfg = {
|
||||||
|
diagnostics: { flags: [" Telegram.Http ", "cache.*", "CACHE.*"] },
|
||||||
|
} as OpenClawConfig;
|
||||||
|
const env = {
|
||||||
|
OPENCLAW_DIAGNOSTICS: " foo, Cache.* telegram.http ",
|
||||||
|
} as NodeJS.ProcessEnv;
|
||||||
|
|
||||||
|
expect(resolveDiagnosticFlags(cfg, env)).toEqual(["telegram.http", "cache.*", "foo"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats false-like env values as no extra flags", () => {
|
||||||
|
const cfg = {
|
||||||
|
diagnostics: { flags: ["telegram.http"] },
|
||||||
|
} as OpenClawConfig;
|
||||||
|
|
||||||
|
for (const raw of ["0", "false", "off", "none", " "]) {
|
||||||
|
expect(
|
||||||
|
resolveDiagnosticFlags(cfg, {
|
||||||
|
OPENCLAW_DIAGNOSTICS: raw,
|
||||||
|
} as NodeJS.ProcessEnv),
|
||||||
|
).toEqual(["telegram.http"]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("matchesDiagnosticFlag", () => {
|
||||||
|
it("matches exact, namespace, prefix, and wildcard rules", () => {
|
||||||
|
expect(matchesDiagnosticFlag("telegram.http", ["telegram.http"])).toBe(true);
|
||||||
|
expect(matchesDiagnosticFlag("cache", ["cache.*"])).toBe(true);
|
||||||
|
expect(matchesDiagnosticFlag("cache.hit", ["cache.*"])).toBe(true);
|
||||||
|
expect(matchesDiagnosticFlag("tool.exec.fast", ["tool.exec*"])).toBe(true);
|
||||||
|
expect(matchesDiagnosticFlag("anything", ["all"])).toBe(true);
|
||||||
|
expect(matchesDiagnosticFlag("anything", ["*"])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects blank and non-matching flags", () => {
|
||||||
|
expect(matchesDiagnosticFlag(" ", ["*"])).toBe(false);
|
||||||
|
expect(matchesDiagnosticFlag("cache.hit", ["cache.miss", "tool.*"])).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isDiagnosticFlagEnabled", () => {
|
||||||
|
it("resolves config and env together before matching", () => {
|
||||||
|
const cfg = {
|
||||||
|
diagnostics: { flags: ["gateway.*"] },
|
||||||
|
} as OpenClawConfig;
|
||||||
|
const env = {
|
||||||
|
OPENCLAW_DIAGNOSTICS: "telegram.http",
|
||||||
|
} as NodeJS.ProcessEnv;
|
||||||
|
|
||||||
|
expect(isDiagnosticFlagEnabled("gateway.ws", cfg, env)).toBe(true);
|
||||||
|
expect(isDiagnosticFlagEnabled("telegram.http", cfg, env)).toBe(true);
|
||||||
|
expect(isDiagnosticFlagEnabled("slack.http", cfg, env)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,38 +1,9 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { OpenClawConfig } from "../config/config.js";
|
|
||||||
import { isDiagnosticFlagEnabled, resolveDiagnosticFlags } from "./diagnostic-flags.js";
|
|
||||||
import { isMainModule } from "./is-main.js";
|
import { isMainModule } from "./is-main.js";
|
||||||
import { buildNodeShellCommand } from "./node-shell.js";
|
import { buildNodeShellCommand } from "./node-shell.js";
|
||||||
import { parseSshTarget } from "./ssh-tunnel.js";
|
import { parseSshTarget } from "./ssh-tunnel.js";
|
||||||
|
|
||||||
describe("infra parsing", () => {
|
describe("infra parsing", () => {
|
||||||
describe("diagnostic flags", () => {
|
|
||||||
it("merges config + env flags", () => {
|
|
||||||
const cfg = {
|
|
||||||
diagnostics: { flags: ["telegram.http", "cache.*"] },
|
|
||||||
} as OpenClawConfig;
|
|
||||||
const env = {
|
|
||||||
OPENCLAW_DIAGNOSTICS: "foo,bar",
|
|
||||||
} as NodeJS.ProcessEnv;
|
|
||||||
|
|
||||||
const flags = resolveDiagnosticFlags(cfg, env);
|
|
||||||
expect(flags).toEqual(expect.arrayContaining(["telegram.http", "cache.*", "foo", "bar"]));
|
|
||||||
expect(isDiagnosticFlagEnabled("telegram.http", cfg, env)).toBe(true);
|
|
||||||
expect(isDiagnosticFlagEnabled("cache.hit", cfg, env)).toBe(true);
|
|
||||||
expect(isDiagnosticFlagEnabled("foo", cfg, env)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("treats env true as wildcard", () => {
|
|
||||||
const env = { OPENCLAW_DIAGNOSTICS: "1" } as NodeJS.ProcessEnv;
|
|
||||||
expect(isDiagnosticFlagEnabled("anything.here", undefined, env)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("treats env false as disabled", () => {
|
|
||||||
const env = { OPENCLAW_DIAGNOSTICS: "0" } as NodeJS.ProcessEnv;
|
|
||||||
expect(isDiagnosticFlagEnabled("telegram.http", undefined, env)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("isMainModule", () => {
|
describe("isMainModule", () => {
|
||||||
it("returns true when argv[1] matches current file", () => {
|
it("returns true when argv[1] matches current file", () => {
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { withTempDir } from "../test-utils/temp-dir.js";
|
import { withTempDir } from "../test-utils/temp-dir.js";
|
||||||
import {
|
|
||||||
getChannelActivity,
|
|
||||||
recordChannelActivity,
|
|
||||||
resetChannelActivityForTest,
|
|
||||||
} from "./channel-activity.js";
|
|
||||||
import { createDedupeCache } from "./dedupe.js";
|
import { createDedupeCache } from "./dedupe.js";
|
||||||
import {
|
import {
|
||||||
emitDiagnosticEvent,
|
emitDiagnosticEvent,
|
||||||
@@ -145,50 +140,6 @@ describe("infra store", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("channel activity", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
resetChannelActivityForTest();
|
|
||||||
vi.useFakeTimers();
|
|
||||||
vi.setSystemTime(new Date("2026-01-08T00:00:00Z"));
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("records inbound/outbound separately", () => {
|
|
||||||
recordChannelActivity({ channel: "telegram", direction: "inbound" });
|
|
||||||
vi.advanceTimersByTime(1000);
|
|
||||||
recordChannelActivity({ channel: "telegram", direction: "outbound" });
|
|
||||||
const res = getChannelActivity({ channel: "telegram" });
|
|
||||||
expect(res.inboundAt).toBe(1767830400000);
|
|
||||||
expect(res.outboundAt).toBe(1767830401000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("isolates accounts", () => {
|
|
||||||
recordChannelActivity({
|
|
||||||
channel: "whatsapp",
|
|
||||||
accountId: "a",
|
|
||||||
direction: "inbound",
|
|
||||||
at: 1,
|
|
||||||
});
|
|
||||||
recordChannelActivity({
|
|
||||||
channel: "whatsapp",
|
|
||||||
accountId: "b",
|
|
||||||
direction: "inbound",
|
|
||||||
at: 2,
|
|
||||||
});
|
|
||||||
expect(getChannelActivity({ channel: "whatsapp", accountId: "a" })).toEqual({
|
|
||||||
inboundAt: 1,
|
|
||||||
outboundAt: null,
|
|
||||||
});
|
|
||||||
expect(getChannelActivity({ channel: "whatsapp", accountId: "b" })).toEqual({
|
|
||||||
inboundAt: 2,
|
|
||||||
outboundAt: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("createDedupeCache", () => {
|
describe("createDedupeCache", () => {
|
||||||
it("marks duplicates within TTL", () => {
|
it("marks duplicates within TTL", () => {
|
||||||
const cache = createDedupeCache({ ttlMs: 1000, maxSize: 10 });
|
const cache = createDedupeCache({ ttlMs: 1000, maxSize: 10 });
|
||||||
|
|||||||
Reference in New Issue
Block a user