管理台: - 内容管理新增名称搜索(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 个全部通过
123 lines
4.0 KiB
JavaScript
123 lines
4.0 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { withApp } from "./helpers.js";
|
|
|
|
test("seeded entries are searchable", async () => {
|
|
await withApp(async ({ api }) => {
|
|
const res = await api("/api/entries?q=ripgrep");
|
|
assert.equal(res.status, 200);
|
|
assert.ok(res.json.data.total >= 1);
|
|
assert.equal(res.json.data.items[0].title, "ripgrep");
|
|
});
|
|
});
|
|
|
|
test("random endpoint returns an active entry", async () => {
|
|
await withApp(async ({ api }) => {
|
|
const res = await api("/api/entries/random");
|
|
assert.equal(res.status, 200);
|
|
assert.equal(res.json.data.status, "active");
|
|
});
|
|
});
|
|
|
|
test("invalid status transition is rejected", async () => {
|
|
await withApp(async ({ api, loginAdmin }) => {
|
|
const cookie = await loginAdmin();
|
|
const found = await api("/api/entries?q=ripgrep");
|
|
const id = found.json.data.items[0].id;
|
|
const res = await api(`/api/entries/${id}/status`, {
|
|
method: "PATCH",
|
|
body: { status: "active" },
|
|
cookie,
|
|
});
|
|
assert.equal(res.status, 400);
|
|
});
|
|
});
|
|
|
|
test("greyed -> active is allowed", async () => {
|
|
await withApp(async ({ api, loginAdmin }) => {
|
|
const cookie = await loginAdmin();
|
|
const found = await api("/api/entries?q=ripgrep");
|
|
const id = found.json.data.items[0].id;
|
|
const grey = await api(`/api/entries/${id}/status`, {
|
|
method: "PATCH",
|
|
body: { status: "greyed" },
|
|
cookie,
|
|
});
|
|
assert.equal(grey.json.data.status, "greyed");
|
|
const back = await api(`/api/entries/${id}/status`, {
|
|
method: "PATCH",
|
|
body: { status: "active" },
|
|
cookie,
|
|
});
|
|
assert.equal(back.json.data.status, "active");
|
|
});
|
|
});
|
|
|
|
test("AI ingest without a valid key is forbidden", async () => {
|
|
await withApp(async ({ api }) => {
|
|
const res = await api("/api/ingest/entry", {
|
|
method: "POST",
|
|
body: { title: "x", url: "https://github.com/a/b", type: "工具" },
|
|
});
|
|
assert.equal(res.status, 403);
|
|
});
|
|
});
|
|
|
|
test("AI ingest accepts X-Ingest-Key header", async () => {
|
|
await withApp(async ({ api }) => {
|
|
const res = await api("/api/ingest/entry", {
|
|
method: "POST",
|
|
body: { title: "probe", url: "https://github.com/x/key", type: "工具" },
|
|
headers: { "X-Ingest-Key": "secret-key" },
|
|
});
|
|
assert.equal(res.status, 201);
|
|
assert.equal(res.json.data.created, true);
|
|
assert.equal(res.json.data.status, "pending");
|
|
}, { aiIngestKey: "secret-key" });
|
|
});
|
|
|
|
test("AI ingest accepts Authorization: Bearer header", async () => {
|
|
await withApp(async ({ api }) => {
|
|
const res = await api("/api/ingest/entry", {
|
|
method: "POST",
|
|
body: { title: "probe", url: "https://github.com/x/bearer", type: "工具" },
|
|
headers: { Authorization: "Bearer secret-key" },
|
|
});
|
|
assert.equal(res.status, 201);
|
|
assert.equal(res.json.data.created, true);
|
|
}, { aiIngestKey: "secret-key" });
|
|
});
|
|
|
|
test("AI ingest accepts raw Authorization token", async () => {
|
|
await withApp(async ({ api }) => {
|
|
const res = await api("/api/ingest/entry", {
|
|
method: "POST",
|
|
body: { title: "probe", url: "https://github.com/x/raw", type: "工具" },
|
|
headers: { Authorization: "secret-key" },
|
|
});
|
|
assert.equal(res.status, 201);
|
|
assert.equal(res.json.data.created, true);
|
|
}, { aiIngestKey: "secret-key" });
|
|
});
|
|
|
|
test("AI ingest rejects a wrong Authorization token", async () => {
|
|
await withApp(async ({ api }) => {
|
|
const res = await api("/api/ingest/entry", {
|
|
method: "POST",
|
|
body: { title: "probe", url: "https://github.com/x/wrong", type: "工具" },
|
|
headers: { Authorization: "Bearer nope" },
|
|
});
|
|
assert.equal(res.status, 403);
|
|
}, { aiIngestKey: "secret-key" });
|
|
});
|
|
|
|
test("unauthenticated writes to entries are forbidden", async () => {
|
|
await withApp(async ({ api }) => {
|
|
const res = await api("/api/entries", {
|
|
method: "POST",
|
|
body: { title: "x", url: "https://github.com/a/c", type: "工具" },
|
|
});
|
|
assert.equal(res.status, 403);
|
|
});
|
|
});
|