Files
emdash-patch-imageupload/packages/core/tests/unit/api/rev.test.ts
kunthawat 2d1be52177 Emdash source with visual editor image upload fix
Fixes:
1. media.ts: wrap placeholder generation in try-catch
2. toolbar.ts: check r.ok, display error message in popover
2026-05-03 10:44:54 +07:00

134 lines
3.8 KiB
TypeScript

/**
* Unit tests for _rev token generation and validation.
*/
import { describe, it, expect } from "vitest";
import { encodeRev, decodeRev, validateRev } from "../../../src/api/rev.js";
import type { ContentItem } from "../../../src/database/repositories/types.js";
function makeItem(overrides: Partial<ContentItem> = {}): ContentItem {
return {
id: "item_1",
type: "posts",
slug: "test",
status: "draft",
data: {},
authorId: null,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-15T12:30:00.000Z",
publishedAt: null,
scheduledAt: null,
liveRevisionId: null,
draftRevisionId: null,
version: 3,
...overrides,
};
}
describe("encodeRev", () => {
it("produces a base64-encoded string", () => {
const item = makeItem();
const rev = encodeRev(item);
expect(rev).toBeTruthy();
// Should be valid base64
expect(() => atob(rev)).not.toThrow();
});
it("encodes version and updatedAt", () => {
const item = makeItem({ version: 5, updatedAt: "2026-02-14T10:00:00.000Z" });
const rev = encodeRev(item);
const decoded = atob(rev);
expect(decoded).toBe("5:2026-02-14T10:00:00.000Z");
});
it("produces different revs for different versions", () => {
const rev1 = encodeRev(makeItem({ version: 1 }));
const rev2 = encodeRev(makeItem({ version: 2 }));
expect(rev1).not.toBe(rev2);
});
it("produces different revs for different updatedAt", () => {
const rev1 = encodeRev(makeItem({ updatedAt: "2026-01-01T00:00:00.000Z" }));
const rev2 = encodeRev(makeItem({ updatedAt: "2026-01-02T00:00:00.000Z" }));
expect(rev1).not.toBe(rev2);
});
});
describe("decodeRev", () => {
it("decodes a valid rev", () => {
const rev = btoa("5:2026-02-14T10:00:00.000Z");
const result = decodeRev(rev);
expect(result).not.toBeNull();
expect(result!.version).toBe(5);
expect(result!.updatedAt).toBe("2026-02-14T10:00:00.000Z");
});
it("returns null for invalid base64", () => {
expect(decodeRev("not-valid-base64!!!")).toBeNull();
});
it("returns null for missing colon", () => {
expect(decodeRev(btoa("nocolon"))).toBeNull();
});
it("returns null for non-numeric version", () => {
expect(decodeRev(btoa("abc:2026-01-01"))).toBeNull();
});
it("round-trips with encodeRev", () => {
const item = makeItem({ version: 7, updatedAt: "2026-03-01T08:15:30.000Z" });
const rev = encodeRev(item);
const decoded = decodeRev(rev);
expect(decoded).not.toBeNull();
expect(decoded!.version).toBe(7);
expect(decoded!.updatedAt).toBe("2026-03-01T08:15:30.000Z");
});
});
describe("validateRev", () => {
it("returns valid when no rev is provided", () => {
const result = validateRev(undefined, makeItem());
expect(result.valid).toBe(true);
});
it("returns valid when rev matches", () => {
const item = makeItem({ version: 3, updatedAt: "2026-01-15T12:30:00.000Z" });
const rev = encodeRev(item);
const result = validateRev(rev, item);
expect(result.valid).toBe(true);
});
it("returns invalid when version mismatches", () => {
const item = makeItem({ version: 3, updatedAt: "2026-01-15T12:30:00.000Z" });
const staleRev = btoa("2:2026-01-15T12:30:00.000Z"); // Version 2, but item is at 3
const result = validateRev(staleRev, item);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.message).toContain("modified");
}
});
it("returns invalid when updatedAt mismatches", () => {
const item = makeItem({ version: 3, updatedAt: "2026-01-15T12:30:00.000Z" });
const staleRev = btoa("3:2026-01-14T00:00:00.000Z"); // Right version, wrong timestamp
const result = validateRev(staleRev, item);
expect(result.valid).toBe(false);
});
it("returns invalid for malformed rev", () => {
const result = validateRev("garbage", makeItem());
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.message).toContain("Malformed");
}
});
});