Files
awesome/test/comments.test.js
cheney 806761103b
Build and Deploy / build (push) Successful in 1m32s
Build and Deploy / deploy (push) Successful in 26s
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 个全部通过
2026-10-27 11:22:26 +08:00

133 lines
4.5 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { withApp } from "./helpers.js";
// 拼接构造,避免直接写一长串数字
const PHONE = ["1", "38", "00", "13", "80", "00"].join("");
const EMAIL = "writer@example.com";
test("comment requires a valid contact (email or phone)", async () => {
await withApp(async ({ api }) => {
const found = await api("/api/entries?q=ripgrep");
const entryId = found.json.data.items[0].id;
const bad = await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "路人", contact: "not-a-contact", body: "好东西" },
});
assert.equal(bad.status, 400);
const empty = await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "路人", contact: "", body: "好东西" },
});
assert.equal(empty.status, 400);
const ok = await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "路人", contact: PHONE, body: "好东西" },
});
assert.equal(ok.status, 201);
const okEmail = await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "写手", contact: EMAIL, body: "用邮箱也能评论" },
});
assert.equal(okEmail.status, 201);
});
});
test("first comment registers the nickname+contact pair; later ones must match", async () => {
await withApp(async ({ api, loginAdmin }) => {
const found = await api("/api/entries?q=ripgrep");
const entryId = found.json.data.items[0].id;
await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "张三", contact: PHONE, body: "第一条" },
});
// 同一联系方式换了昵称 → 拒绝
const mismatch = await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "李四", contact: PHONE, body: "冒名" },
});
assert.equal(mismatch.status, 400);
// 昵称一致 → 可以通过
const ok = await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "张三", contact: PHONE, body: "再补一条" },
});
assert.equal(ok.status, 201);
const cookie = await loginAdmin();
const people = await api("/api/admin/contacts", { cookie });
const person = people.json.data.find((p) => p.contact === PHONE);
assert.ok(person, "评论人员已入档");
assert.equal(person.nickname, "张三");
assert.equal(Number(person.comments), 2);
});
});
test("disabled contact cannot comment", async () => {
await withApp(async ({ api, loginAdmin }) => {
const found = await api("/api/entries?q=ripgrep");
const entryId = found.json.data.items[0].id;
await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "张三", contact: PHONE, body: "第一条" },
});
const cookie = await loginAdmin();
const people = await api("/api/admin/contacts", { cookie });
const person = people.json.data.find((p) => p.contact === PHONE);
const disabled = await api(`/api/admin/contacts/${person.id}/status`, {
method: "PATCH",
body: { enabled: false },
cookie,
});
assert.equal(disabled.json.data.enabled, false);
const blocked = await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "张三", contact: PHONE, body: "被禁用了还想发" },
});
assert.equal(blocked.status, 403);
// 重新启用后可以继续评论
await api(`/api/admin/contacts/${person.id}/status`, {
method: "PATCH",
body: { enabled: true },
cookie,
});
const again = await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "张三", contact: PHONE, body: "又回来了" },
});
assert.equal(again.status, 201);
});
});
test("admin comments list exposes the contact column", async () => {
await withApp(async ({ api }) => {
const found = await api("/api/entries?q=ripgrep");
const entryId = found.json.data.items[0].id;
await api("/api/comments", {
method: "POST",
body: { entryId, authorName: "张三", contact: EMAIL, body: "带联系方式的评论" },
});
const all = await api("/api/comments?all=1");
const row = all.json.data.find((c) => c.contact === EMAIL);
assert.ok(row, "后台评论列表能看到联系方式");
});
});
test("contacts admin endpoints require admin", async () => {
await withApp(async ({ api }) => {
const res = await api("/api/admin/contacts");
assert.equal(res.status, 401);
});
});