Files
NetMesh/domain/searchMatcher.test.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

178 lines
4.9 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
PINYIN_CACHE_MAX_ENTRIES,
getPinyinCacheStatsForTests,
getHostSearchMatch,
matchesHostSearchQuery,
matchesSearchQuery,
resetPinyinCacheForTests,
} from "../lib/searchMatcher.ts";
test("matches mixed Chinese and dash-separated numeric suffix with spaced query", () => {
assert.equal(
matchesSearchQuery("山东 6-1", "山东-业务交换机6-1"),
true,
);
});
test("matches mixed Chinese and em-dash separator with spaced query", () => {
assert.equal(
matchesSearchQuery("山东 6-1", "山东—业务交换机6—1"),
true,
);
});
test("matches IPv4-like query only on contiguous dotted address", () => {
assert.equal(
matchesSearchQuery("192.168.6.1", "192.168.6.1"),
true,
);
assert.equal(
matchesSearchQuery("192.168.6.1", "192.168.16.10"),
false,
);
});
test("matches compact form across separators", () => {
assert.equal(
matchesSearchQuery("prod api 01", "prod-api-01"),
true,
);
});
test("host search does not mix human tokens with hostname IP tokens", () => {
assert.equal(
matchesHostSearchQuery("山东 6-1", {
label: "山东-业务交换机2-2",
hostname: "10.6.1.88",
group: "铁塔网络设备/山东",
tags: [],
}),
false,
);
});
test("host search treats equivalent dash separators as strict punctuation matches", () => {
const match = getHostSearchMatch("山东 6-1", {
label: "山东—业务交换机6—1",
hostname: "10.6.1.88",
group: "铁塔/山东",
tags: [],
});
assert.equal(match.matched, true);
assert.equal(match.phase, "strict");
});
test("host search still supports direct IP matching", () => {
assert.equal(
matchesHostSearchQuery("10.6.1.88", {
label: "山东-业务交换机2-2",
hostname: "10.6.1.88",
group: "铁塔网络设备/山东",
tags: [],
}),
true,
);
});
test("host search keeps trailing dash semantic and avoids loose numeric fallback", () => {
assert.equal(
matchesHostSearchQuery("山东 6-", {
label: "山东-IPMI交换机6",
hostname: "10.6.1.88",
group: "铁塔/山东",
tags: [],
}),
false,
);
assert.equal(
matchesHostSearchQuery("山东 6-", {
label: "山东-管理交换机6-1",
hostname: "10.6.1.81",
group: "铁塔/山东",
tags: [],
}),
true,
);
});
test("host search scoring prefers strict punctuation match over loose compact match", () => {
const strict = getHostSearchMatch("山东 6-", {
label: "山东-管理交换机6-1",
hostname: "10.6.1.81",
group: "铁塔/山东",
tags: [],
});
const loose = getHostSearchMatch("山东 61", {
label: "山东-管理交换机6-1",
hostname: "10.6.1.81",
group: "铁塔/山东",
tags: [],
});
assert.equal(strict.matched, true);
assert.equal(loose.matched, true);
assert.equal(strict.phase, "strict");
assert.equal(loose.phase, "loose");
assert.equal(strict.score > loose.score, true);
});
test("punctuation-only query does not match every field", () => {
assert.equal(matchesSearchQuery("---", "prod-api-01"), false);
assert.equal(matchesSearchQuery("-", "some-host"), false);
});
test("host search avoids compact hostname false positives on numeric segments", () => {
assert.equal(
matchesHostSearchQuery("61", {
label: "核心交换机",
hostname: "10.6.1.88",
group: "网络设备",
tags: [],
}),
false,
);
});
test("host search scoring favors label over group when both match", () => {
const labelHit = getHostSearchMatch("山东 6-1", {
label: "山东-业务交换机6-1",
hostname: "10.8.2.10",
group: "网络设备/核心",
tags: [],
});
const groupHit = getHostSearchMatch("山东 6-1", {
label: "核心交换机",
hostname: "10.8.2.11",
group: "山东/业务交换机6-1",
tags: [],
});
assert.equal(labelHit.matched, true);
assert.equal(groupHit.matched, true);
assert.equal(labelHit.score > groupHit.score, true);
});
test("pinyin cache covers 8000 hosts and stays hard-bounded through import/edit churn", () => {
resetPinyinCacheForTests();
const startedAt = performance.now();
for (let index = 0; index < 8_000; index += 1) {
matchesHostSearchQuery("z", { label: `主机${index}` });
}
const firstPassMs = performance.now() - startedAt;
assert.equal(getPinyinCacheStatsForTests().size, 8_000);
assert.ok(firstPassMs < 10_000, `8000-host pinyin indexing took ${firstPassMs.toFixed(1)}ms`);
matchesHostSearchQuery("z", { label: "主机0" });
for (let index = 8_000; index < PINYIN_CACHE_MAX_ENTRIES + 128; index += 1) {
matchesHostSearchQuery("z", { label: `主机${index}` });
}
const stats = getPinyinCacheStatsForTests();
assert.equal(stats.size, PINYIN_CACHE_MAX_ENTRIES);
assert.ok(stats.keys.includes("主机0"));
assert.ok(!stats.keys.includes("主机1"));
resetPinyinCacheForTests();
});