服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
121 lines
4.6 KiB
TypeScript
121 lines
4.6 KiB
TypeScript
import { afterEach, beforeEach, expect, test, vi } from "vitest";
|
|
import { isReactive, watch } from "vue";
|
|
import { createPinia, setActivePinia } from "pinia";
|
|
import { formatLogValue, logCapacity, useLogStore } from "../src/stores/logs";
|
|
|
|
beforeEach(() => setActivePinia(createPinia()));
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
function backend(id: number, level = "INFO") {
|
|
return { id, timestamp: "2026-10-07T10:00:00Z", source: "Go", level, message: `entry ${id}` };
|
|
}
|
|
|
|
test("records while collapsed and filters exact selected levels", () => {
|
|
const logs = useLogStore();
|
|
logs.add("DEBUG", "test", "debug");
|
|
logs.add("INFO", "test", "info");
|
|
logs.add("WARN", "test", "warning");
|
|
logs.add("ERROR", "test", new Error("failure"));
|
|
expect(logs.expanded).toBe(false);
|
|
expect(logs.entries).toHaveLength(4);
|
|
expect(logs.filtered).toHaveLength(3);
|
|
expect(logs.errorCount).toBe(1);
|
|
logs.levels = ["ERROR", "DEBUG"];
|
|
expect(logs.filtered.map((entry) => entry.level)).toEqual(["DEBUG", "ERROR"]);
|
|
logs.levels = [];
|
|
expect(logs.filtered).toEqual([]);
|
|
});
|
|
|
|
test("history and live events are deduplicated and backend order is restored", () => {
|
|
const logs = useLogStore();
|
|
logs.ingest(backend(3));
|
|
logs.ingest([backend(1), backend(2), backend(3)]);
|
|
expect(logs.entries.map((entry) => entry.backendID)).toEqual([1, 2, 3]);
|
|
logs.ingest(backend(2));
|
|
expect(logs.entries).toHaveLength(3);
|
|
});
|
|
|
|
test("retains only the latest entries", () => {
|
|
const logs = useLogStore();
|
|
logs.ingest(Array.from({ length: logCapacity + 50 }, (_, i) => backend(i + 1)));
|
|
expect(logs.entries).toHaveLength(logCapacity);
|
|
expect(logs.entries[0].backendID).toBe(51);
|
|
logs.add("ERROR", "test", "latest");
|
|
expect(logs.entries).toHaveLength(logCapacity);
|
|
expect(logs.entries.at(-1)?.message).toBe("latest");
|
|
});
|
|
|
|
test("clear prevents delayed history replay from restoring cleared entries", () => {
|
|
const logs = useLogStore();
|
|
logs.ingest(backend(10));
|
|
logs.clear();
|
|
logs.ingest([backend(5), backend(10), backend(11)]);
|
|
expect(logs.entries.map((entry) => entry.backendID)).toEqual([11]);
|
|
logs.add("ERROR", "test", "new error");
|
|
expect(logs.errorCount).toBe(1);
|
|
logs.clear();
|
|
expect(logs.errorCount).toBe(0);
|
|
});
|
|
|
|
test("handles ERR, unknown levels and malformed events", () => {
|
|
const logs = useLogStore();
|
|
logs.ingest([null, {}, { ...backend(0) }, backend(1, "ERR"), backend(2, "unknown")]);
|
|
expect(logs.entries.map((entry) => entry.level)).toEqual(["ERROR", "INFO"]);
|
|
});
|
|
|
|
test("formats exceptions, objects and circular console arguments", () => {
|
|
expect(formatLogValue(new Error("failure"))).toContain("failure");
|
|
expect(formatLogValue({ state: "error" })).toBe('{"state":"error"}');
|
|
const circular: { self?: unknown } = {};
|
|
circular.self = circular;
|
|
expect(formatLogValue(circular)).toBe("[object Object]");
|
|
});
|
|
|
|
test("日志突发异步合并为一次界面更新,并保留容量内的顺序与错误", () => {
|
|
vi.useFakeTimers();
|
|
const logs = useLogStore();
|
|
let publications = 0;
|
|
const stop = watch(() => logs.entries, () => publications++, { flush: "sync" });
|
|
for (let id = 4000; id > 0; id--) logs.enqueueBackendLogs(backend(id, id === 4000 ? "ERROR" : "DEBUG"));
|
|
expect(logs.entries).toHaveLength(0);
|
|
vi.runAllTimers();
|
|
expect(publications).toBe(1);
|
|
expect(logs.entries).toHaveLength(logCapacity);
|
|
expect(logs.entries[0].backendID).toBe(2001);
|
|
expect(logs.entries.at(-1)?.backendID).toBe(4000);
|
|
expect(logs.errorCount).toBe(1);
|
|
expect(isReactive(logs.entries[0])).toBe(false);
|
|
const previous = logs.entries;
|
|
logs.add("INFO", "test", "new entry");
|
|
expect(previous.at(-1)?.backendID).toBe(4000);
|
|
stop();
|
|
});
|
|
|
|
test("清空时取消待发布日志,延迟历史不会重新出现", () => {
|
|
vi.useFakeTimers();
|
|
const logs = useLogStore();
|
|
logs.enqueueBackendLogs([backend(10), backend(11)]);
|
|
logs.clear();
|
|
logs.enqueueBackendLogs([backend(10), backend(11), backend(12)]);
|
|
vi.runAllTimers();
|
|
expect(logs.entries.map((entry) => entry.backendID)).toEqual([12]);
|
|
});
|
|
|
|
test("生产日志保留操作结果和异常,跳过 DEBUG 与 Go 运行时信息", () => {
|
|
vi.stubEnv("DEV", false);
|
|
vi.stubEnv("MODE", "production");
|
|
const logs = useLogStore();
|
|
const format = vi.fn(() => "debug details");
|
|
logs.add("DEBUG", "performance", { toJSON: format });
|
|
logs.ingest([
|
|
backend(1, "DEBUG"), backend(2, "INFO"), backend(3, "WARN"), backend(4, "ERROR"),
|
|
{ ...backend(5, "INFO"), source: "archive:saved" },
|
|
{ ...backend(6, "INFO"), source: undefined },
|
|
]);
|
|
expect(format).not.toHaveBeenCalled();
|
|
expect(logs.entries.map((entry) => entry.backendID)).toEqual([3, 4, 5]);
|
|
});
|