forked from EduCraft/curriculum-project-hub
feat(filelib)!: VersionStore 改为真 git,一项目一仓库
内存 store 换成 gitVersionStore:init 建目录并 git init,VersionId 是
commit hash,某文件的版本取 `git log -1 -- <path>`(D16 文件级版本不因
别的文件提交而失效)。删除也是一个 commit,旧版本仍可读。决策见 ADR-0030。
git 用 execFile 调系统二进制,不引依赖。每次调用钉死 --git-dir/--work-tree
并禁 hooks、隔离全局 gitconfig:项目仓库是老师上传的数据,而 storage root
默认就在本 repo 内,不钉死会让命令落到外层仓库上。
同时:
- 单文件上限改为 HUB_FILELIB_MAX_FILE_BYTES(缺省 10MiB),前端从
/database/config 读,不再两处硬编码
- commit 身份 name=displayName、email=<userId>@filelib.paradigm-edu.net;
message 缺省为「【用户名】修改了【路径】」,调用方显式传则优先
- 上传改走弹窗,路径与 commit 信息可手填(原先 prompt 只能填路径)
BREAKING CHANGE: VersionId 由计数器(v1/v2)变为 commit hash;
CommitRequest.author 由字符串变为 { userId, displayName? }。
旧 .version-store.json 不迁移,此前建的项目报 repo_not_found。
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 单文件上限的环境变量解析 + commit message 默认文案。
|
||||
*
|
||||
* 上限:`HUB_FILELIB_MAX_FILE_BYTES` 覆盖,缺省 10MiB;非法值按缺省 ——
|
||||
* 配置写错不该让上传静默变成 0 上限(那会把每次上传都拒掉)。
|
||||
* 文案:`【用户名】修改了【路径】`,用户名取 displayName、缺失回退 userId。
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
FILE_CONTENT_MAX_BYTES_DEFAULT,
|
||||
defaultCommitMessage,
|
||||
resolveMaxFileBytes,
|
||||
} from "../../src/database/filelib/fileService.js";
|
||||
|
||||
describe("resolveMaxFileBytes", () => {
|
||||
it("缺省 10MiB", () => {
|
||||
expect(FILE_CONTENT_MAX_BYTES_DEFAULT).toBe(10 * 1024 * 1024);
|
||||
expect(resolveMaxFileBytes(undefined)).toBe(FILE_CONTENT_MAX_BYTES_DEFAULT);
|
||||
expect(resolveMaxFileBytes("")).toBe(FILE_CONTENT_MAX_BYTES_DEFAULT);
|
||||
expect(resolveMaxFileBytes(" ")).toBe(FILE_CONTENT_MAX_BYTES_DEFAULT);
|
||||
});
|
||||
|
||||
it("合法正整数生效(可上调也可下调)", () => {
|
||||
expect(resolveMaxFileBytes("1048576")).toBe(1024 * 1024);
|
||||
expect(resolveMaxFileBytes(" 52428800 ")).toBe(50 * 1024 * 1024);
|
||||
expect(resolveMaxFileBytes("1")).toBe(1);
|
||||
});
|
||||
|
||||
it("非法值一律回退缺省,不产生 0 或负数上限", () => {
|
||||
for (const bad of ["0", "-1", "abc", "1.5", "NaN", "Infinity", "1e999", "10MB"]) {
|
||||
expect(resolveMaxFileBytes(bad)).toBe(FILE_CONTENT_MAX_BYTES_DEFAULT);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaultCommitMessage", () => {
|
||||
it("【用户名】修改了【路径】,用户名取 displayName", () => {
|
||||
expect(defaultCommitMessage({ userId: "u1", isWebsiteAdmin: false, displayName: "张老师" }, "讲义/第一课.md"))
|
||||
.toBe("【张老师】修改了【讲义/第一课.md】");
|
||||
});
|
||||
|
||||
it("displayName 缺失/空白时回退 userId", () => {
|
||||
expect(defaultCommitMessage({ userId: "u_alice", isWebsiteAdmin: false }, "a.md"))
|
||||
.toBe("【u_alice】修改了【a.md】");
|
||||
expect(defaultCommitMessage({ userId: "u_alice", isWebsiteAdmin: false, displayName: " " }, "a.md"))
|
||||
.toBe("【u_alice】修改了【a.md】");
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* message 优先级的纯函数复刻(commitFile 里的那三行):
|
||||
* 非空则用调用方的,否则回退默认文案。空串必须算“没传” ——
|
||||
* `git commit -m ""` 会以 empty commit message 失败。
|
||||
*/
|
||||
function resolveMessage(input: string | undefined, fallback: string): string {
|
||||
const trimmed = input?.trim();
|
||||
return trimmed === undefined || trimmed === "" ? fallback : trimmed;
|
||||
}
|
||||
|
||||
describe("commit message 优先级", () => {
|
||||
const fallback = "【张老师】修改了【a.md】";
|
||||
|
||||
it("手填了就用手填的", () => {
|
||||
expect(resolveMessage("补上第三课的课件", fallback)).toBe("补上第三课的课件");
|
||||
expect(resolveMessage(" 前后有空格 ", fallback)).toBe("前后有空格");
|
||||
});
|
||||
|
||||
it("未传/空/约空白一律回退默认文案", () => {
|
||||
for (const raw of [undefined, "", " ", "\n\t"]) {
|
||||
expect(resolveMessage(raw, fallback)).toBe(fallback);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user