feat: 管理台搜索/分区刷新、评论人员体系、相关项目,修复内容源自动获取,完善 MCP 自描述
管理台: - 内容管理新增名称搜索(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 个全部通过
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { withApp } from "./helpers.js";
|
||||
|
||||
test("admin content search filters by name", async () => {
|
||||
await withApp(async ({ api, loginAdmin }) => {
|
||||
const cookie = await loginAdmin();
|
||||
const hit = await api("/api/admin/entries?q=ripgrep&status=all", { cookie });
|
||||
assert.equal(hit.json.data.total, 1);
|
||||
assert.equal(hit.json.data.items[0].title, "ripgrep");
|
||||
|
||||
const miss = await api("/api/admin/entries?q=zzzz-not-exist&status=all", { cookie });
|
||||
assert.equal(miss.json.data.total, 0);
|
||||
|
||||
// 也能按 slug 命中
|
||||
const bySlug = await api("/api/admin/entries?q=uptime-kuma&status=all", { cookie });
|
||||
assert.equal(bySlug.json.data.total, 1);
|
||||
});
|
||||
});
|
||||
|
||||
test("admin content page renders the search box and 20 rows per page", async () => {
|
||||
await withApp(async ({ base, api, loginAdmin }) => {
|
||||
const cookie = await loginAdmin();
|
||||
const res = await fetch(`${base}/admin?q=ripgrep`, { headers: { cookie } });
|
||||
const html = await res.text();
|
||||
assert.equal(res.status, 200);
|
||||
assert.ok(html.includes('name="q"'), "有名称搜索框");
|
||||
assert.ok(html.includes("每页 20 条"), "标注每页 20 条");
|
||||
|
||||
const perPage = await api("/api/admin/entries?status=all&page=1", { cookie });
|
||||
assert.equal(perPage.json.data.perPage, 20);
|
||||
});
|
||||
});
|
||||
|
||||
test("admin nav links point at server-rendered sections", async () => {
|
||||
await withApp(async ({ base, loginAdmin }) => {
|
||||
const cookie = await loginAdmin();
|
||||
const res = await fetch(`${base}/admin?section=sec-comments`, { headers: { cookie } });
|
||||
const html = await res.text();
|
||||
assert.equal(res.status, 200);
|
||||
assert.ok(html.includes('href="/admin?section=sec-contacts"'), "菜单是真链接");
|
||||
assert.ok(
|
||||
html.includes('<section id="sec-comments" class="active">'),
|
||||
"服务端直接渲染出当前分区"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("pending entries show 预览 and active entries show 查看", async () => {
|
||||
await withApp(async ({ base, api, loginAdmin }) => {
|
||||
const cookie = await loginAdmin();
|
||||
const active = await fetch(`${base}/admin?status=active`, { headers: { cookie } });
|
||||
const activeHtml = await active.text();
|
||||
assert.ok(activeHtml.includes(">查看</a>"), "active 条目按钮是「查看」");
|
||||
assert.ok(!activeHtml.includes(">预览</a>"), "active 条目不出现「预览」");
|
||||
|
||||
await api("/api/ingest/entry", {
|
||||
method: "POST",
|
||||
body: { title: "pending-probe", url: "https://example.com/pending-probe", type: "工具" },
|
||||
headers: { "X-Ingest-Key": "secret-key" },
|
||||
});
|
||||
const pending = await fetch(`${base}/admin?status=pending`, { headers: { cookie } });
|
||||
const pendingHtml = await pending.text();
|
||||
assert.ok(pendingHtml.includes(">预览</a>"), "pending 条目按钮是「预览」");
|
||||
}, { aiIngestKey: "secret-key" });
|
||||
});
|
||||
|
||||
test("comment form does not prefill a nickname", async () => {
|
||||
await withApp(async ({ base, loginAdmin }) => {
|
||||
const cookie = await loginAdmin();
|
||||
const res = await fetch(`${base}/entries/ripgrep`, { headers: { cookie } });
|
||||
const html = await res.text();
|
||||
const form = html.slice(html.indexOf('class="comment-form"'));
|
||||
assert.ok(form.includes('name="contact"'), "评论表单有联系方式字段");
|
||||
assert.ok(!/name="authorName"[^>]*value="admin"/.test(form), "昵称没有预置 admin");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user