管理台: - 内容管理新增名称搜索(title/slug/url/description),状态与翻页链接保留搜索词 - 菜单改为真实链接,每次点击重新向后台取数渲染分区,不再依赖手动刷新 - 内容管理每页固定 20 条 - pending 条目按钮改为「预览」,active 为「查看」 评论体系: - 评论提交新增必填联系方式(手机号/邮箱),前端不做展示 - 首次评论以「昵称+联系方式」建档,之后需匹配且未被禁用 - 后台评论管理新增联系方式列;新增评论人员管理页,可启用/禁用/改名/删除 - 评论昵称输入框不再预置 admin 相关项目: - 新增 entry_relations 双向关联,详情页侧栏展示并写入机读 JSON - 编辑页可添加/移除关联 内容源自动获取: - 修复 rss 适配器把 feed url 当成条目 url 的根本问题,支持 Atom link/href 与相对链接 - eryajf-weekly 限定正文容器取链接,修正项目说明的提取与「(opens new window)」清理 - pipeline 增加轮内去重与单条失败隔离;scheduler 对非法 cron 明确告警 MCP: - 重写 instructions 为八节说明,新增 usage 工具返回结构化接入文档 - 补全各工具的参数、用途与返回格式说明;baseUrl 取自当次请求 - 首页 MCP 区块与 README 同步补充写入示例与踩坑清单 测试: 新增 comments/relations/admin/ingest-adapters 用例,共 29 个全部通过
57 lines
2.0 KiB
JavaScript
57 lines
2.0 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { getAdapter, normalizeUrl } from "../src/ingest/adapters/index.js";
|
|
|
|
const RSS = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rss version="2.0"><channel>
|
|
<title>Test Feed</title>
|
|
<link>https://example.com/blog</link>
|
|
<item>
|
|
<title>First Post</title>
|
|
<link>https://example.com/posts/first?utm_source=feed</link>
|
|
<description><![CDATA[<p>Some <b>html</b> description</p>]]></description>
|
|
</item>
|
|
<item>
|
|
<title>Relative Link</title>
|
|
<link>/posts/relative</link>
|
|
<description>Relative one</description>
|
|
</item>
|
|
<item>
|
|
<title>Atom style</title>
|
|
<link href="https://example.com/posts/atom" />
|
|
<description>Atom styled link</description>
|
|
</item>
|
|
</channel></rss>`;
|
|
|
|
test("rss adapter resolves each item's own link, not the feed url", async () => {
|
|
// 保留原始 fetch:把 RSS 内容喂进去
|
|
const realFetch = globalThis.fetch;
|
|
globalThis.fetch = async () => new Response(RSS, { status: 200 });
|
|
try {
|
|
const items = await getAdapter("rss").fetch("https://example.com/feed.xml");
|
|
assert.equal(items.length, 3);
|
|
|
|
assert.equal(items[0].title, "First Post");
|
|
// 关键:url 必须是条目的链接,而不是 https://example.com/feed.xml
|
|
assert.equal(items[0].url, "https://example.com/posts/first");
|
|
// utm_ 参数被去掉
|
|
assert.ok(!items[0].url.includes("utm_source"));
|
|
assert.match(items[0].description, /Some html description/);
|
|
|
|
// 相对链接按 feed 地址解析成绝对地址
|
|
assert.equal(items[1].url, "https://example.com/posts/relative");
|
|
|
|
// <link href="..."> 形式也能取到
|
|
assert.equal(items[2].url, "https://example.com/posts/atom");
|
|
|
|
assert.ok(items.every((i) => i.url !== "https://example.com/feed.xml"));
|
|
} finally {
|
|
globalThis.fetch = realFetch;
|
|
}
|
|
});
|
|
|
|
test("normalizeUrl drops tracking params and trailing slash", () => {
|
|
assert.equal(normalizeUrl("https://a.com/x/?utm_src=b#frag"), "https://a.com/x");
|
|
assert.equal(normalizeUrl("https://a.com/x"), "https://a.com/x");
|
|
});
|