fix(merge): PR 集成 - fix merge 上传状态与易支付校验
在 #544、#545、#546 的原作者 merge commit 之后独立修复:保留上传 Promise、处理重复上传与复制失败、格式对齐,以及易支付网关、订单证据、签名、通知幂等与安全关单校验。同步重建六平台插件产物和包。 验证:前端 1832 tests、build/typecheck、lint、相关格式检查通过;财务 SQLite、支付、插件 RPC 与产物专项通过。后端全量出现一项未改动的素材删除 SQLite 表锁失败,单独复跑 3 次通过,保留全量未通过记录。浏览器与真实商户验收待完成。#543 因迁移历史与能力限制回退暂缓。
This commit is contained in:
@@ -4,12 +4,24 @@ import { analyticsFinanceColumns } from "../src/pages/admin/components/analytics
|
||||
|
||||
function renderFinanceCells(finance?: AnalyticsFinance | null) {
|
||||
const row: AdminAnalytics["models"][number] = {
|
||||
model: "example", capability: "text", tasks: 1, requests: 1, uniqueUsers: 1,
|
||||
taskSuccessRate: 100, requestSuccessRate: 100, p50DurationMs: 100, p95DurationMs: 100,
|
||||
inputTokens: 10, outputTokens: 10, cachedTokens: 0, usageAvailable: true,
|
||||
mediaCount: 0, videoSeconds: 0, ...(finance === undefined ? {} : { finance }),
|
||||
model: "example",
|
||||
capability: "text",
|
||||
tasks: 1,
|
||||
requests: 1,
|
||||
uniqueUsers: 1,
|
||||
taskSuccessRate: 100,
|
||||
requestSuccessRate: 100,
|
||||
p50DurationMs: 100,
|
||||
p95DurationMs: 100,
|
||||
inputTokens: 10,
|
||||
outputTokens: 10,
|
||||
cachedTokens: 0,
|
||||
usageAvailable: true,
|
||||
mediaCount: 0,
|
||||
videoSeconds: 0,
|
||||
...(finance === undefined ? {} : { finance }),
|
||||
};
|
||||
return analyticsFinanceColumns.map((column) => "render" in column ? column.render?.(undefined, row, 0) : undefined);
|
||||
return analyticsFinanceColumns.map((column) => ("render" in column ? column.render?.(undefined, row, 0) : undefined));
|
||||
}
|
||||
|
||||
test("model finance cells tolerate missing and null backend finance", () => {
|
||||
@@ -18,16 +30,13 @@ test("model finance cells tolerate missing and null backend finance", () => {
|
||||
});
|
||||
|
||||
test("model finance cells preserve real zero values and unknown margins", () => {
|
||||
expect(renderFinanceCells({ settledOrders: 1, costedOrders: 1, revenueMicrocredits: 0, costMicrocredits: 0, profitMicrocredits: 0, profitMargin: null }))
|
||||
.toEqual(["0", "0", "0", "--"]);
|
||||
expect(renderFinanceCells({ settledOrders: 1, costedOrders: 1, revenueMicrocredits: 0, costMicrocredits: 0, profitMicrocredits: 0, profitMargin: null })).toEqual(["0", "0", "0", "--"]);
|
||||
});
|
||||
|
||||
test("model finance cells show partial costs without inventing profits", () => {
|
||||
expect(renderFinanceCells({ settledOrders: 2, costedOrders: 1, revenueMicrocredits: 1500000, costMicrocredits: 250000, profitMicrocredits: null, profitMargin: null }))
|
||||
.toEqual(["1.5", "0.25(部分)", "--", "--"]);
|
||||
expect(renderFinanceCells({ settledOrders: 2, costedOrders: 1, revenueMicrocredits: 1500000, costMicrocredits: 250000, profitMicrocredits: null, profitMargin: null })).toEqual(["1.5", "0.25(部分)", "--", "--"]);
|
||||
});
|
||||
|
||||
test("model finance cells display known losses", () => {
|
||||
expect(renderFinanceCells({ settledOrders: 1, costedOrders: 1, revenueMicrocredits: 1000000, costMicrocredits: 1500000, profitMicrocredits: -500000, profitMargin: -50 }))
|
||||
.toEqual(["1", "1.5", "-0.5", "-50.0%"]);
|
||||
expect(renderFinanceCells({ settledOrders: 1, costedOrders: 1, revenueMicrocredits: 1000000, costMicrocredits: 1500000, profitMicrocredits: -500000, profitMargin: -50 })).toEqual(["1", "1.5", "-0.5", "-50.0%"]);
|
||||
});
|
||||
|
||||
43
web/test/plugin-upload-merge.test.ts
Normal file
43
web/test/plugin-upload-merge.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { registerPlugin, unregisterPlugin } from "../src/lib/plugins/plugin-registry";
|
||||
import type { PluginManifest } from "../src/lib/plugins/plugin-types";
|
||||
|
||||
test("plugin upload caller preserves the installation promise", async () => {
|
||||
const page = await Bun.file(new URL("../src/pages/admin/plugins/plugins-page.tsx", import.meta.url)).text();
|
||||
expect(page).toContain("onUpload={upload}");
|
||||
expect(page).not.toContain("onUpload={(file) => void upload(file)}");
|
||||
});
|
||||
|
||||
test("plugin upload guards concurrent events and exposes installation failures", async () => {
|
||||
const modal = await Bun.file(new URL("../src/pages/plugins/plugin-documentation-modals.tsx", import.meta.url)).text();
|
||||
expect(modal).toContain("if (uploadInFlight.current) return;");
|
||||
expect(modal.indexOf("uploadInFlight.current = true")).toBeLessThan(modal.indexOf("await onUpload(file)"));
|
||||
expect(modal).toContain("uploadInFlight.current = false");
|
||||
expect(modal).toContain("closable={!uploading}");
|
||||
expect(modal).toContain('message.error(error instanceof Error ? error.message : "安装插件失败")');
|
||||
expect(modal).toContain('aria-pressed={activeTab === "install"}');
|
||||
expect(modal).toContain('aria-pressed={activeTab === "guide"}');
|
||||
expect(modal).toContain("if (!navigator.clipboard) throw new Error");
|
||||
expect(modal).toContain("clearTimeout(copyTimer.current)");
|
||||
});
|
||||
|
||||
test("copied guide manifest is accepted by the real plugin registry", async () => {
|
||||
const guide = await Bun.file(new URL("../src/pages/plugins/plugin-development-guide.md", import.meta.url)).text();
|
||||
const example = guide.match(/```json\s*([\s\S]*?)```/)?.[1].trim();
|
||||
expect(example).toBeTruthy();
|
||||
const manifest = { ...(JSON.parse(example!) as PluginManifest), id: "merge-test-manifest-example" };
|
||||
try {
|
||||
expect(() => registerPlugin({ manifest })).not.toThrow();
|
||||
} finally {
|
||||
unregisterPlugin(manifest.id);
|
||||
}
|
||||
});
|
||||
|
||||
test("reduced motion preserves modal completion events", async () => {
|
||||
const css = await Bun.file(new URL("../src/pages/admin/theme/admin-chrome.css", import.meta.url)).text();
|
||||
const reducedMotion = css.slice(css.lastIndexOf("@media (prefers-reduced-motion: reduce)"));
|
||||
expect(reducedMotion).toContain("animation-duration: 0.01ms !important");
|
||||
expect(reducedMotion).toContain("transition-duration: 0.01ms !important");
|
||||
expect(reducedMotion).not.toContain("animation: none");
|
||||
expect(reducedMotion).not.toContain("transition: none");
|
||||
});
|
||||
Reference in New Issue
Block a user