add complete parent-bff implementation including: - GraphQL endpoint with depth/cost validation - ChildGuard越权校验 with redis cache and singleflight - parallel orchestration with partial failure fallback - three-level cache fallback strategy (Redis + LRU + downstream) - Kafka consumer for cache invalidation and notification push - opossum circuit breaker for downstream services - Prometheus metrics and SLO alerts - Helm chart for k8s deployment with multi-environment configs - Grafana dashboard for observability - complete unit and integration tests
121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
||
import { orchestrate } from "../../src/aggregation/orchestrator.js";
|
||
|
||
describe("Orchestrator", () => {
|
||
describe("全部成功", () => {
|
||
it("返回所有结果且 partial=false", async () => {
|
||
const result = await orchestrate({
|
||
a: Promise.resolve("value-a"),
|
||
b: Promise.resolve(42),
|
||
c: Promise.resolve({ key: "val" }),
|
||
});
|
||
|
||
expect(result.partial).toBe(false);
|
||
expect(result.failures).toHaveLength(0);
|
||
expect(result.results.a).toBe("value-a");
|
||
expect(result.results.b).toBe(42);
|
||
expect(result.results.c).toEqual({ key: "val" });
|
||
});
|
||
});
|
||
|
||
describe("部分失败降级(测试用例 5)", () => {
|
||
it("data-ana 失败时该字段为 null,其他字段正常", async () => {
|
||
const result = await orchestrate({
|
||
parent: Promise.resolve({ id: "parent-001", name: "王家长" }),
|
||
children: Promise.resolve([
|
||
{ id: "student-001", name: "李同学" },
|
||
]),
|
||
analytics: Promise.reject(new Error("data-ana connection refused")),
|
||
});
|
||
|
||
expect(result.partial).toBe(true);
|
||
expect(result.results.parent).toEqual({ id: "parent-001", name: "王家长" });
|
||
expect(result.results.children).toEqual([
|
||
{ id: "student-001", name: "李同学" },
|
||
]);
|
||
expect(result.results.analytics).toBeNull();
|
||
expect(result.failures).toHaveLength(1);
|
||
expect(result.failures[0]!.key).toBe("analytics");
|
||
});
|
||
|
||
it("多个失败都记录在 failures 中", async () => {
|
||
const result = await orchestrate({
|
||
ok: Promise.resolve("ok"),
|
||
fail1: Promise.reject(new Error("err1")),
|
||
fail2: Promise.reject(new Error("err2")),
|
||
});
|
||
|
||
expect(result.partial).toBe(true);
|
||
expect(result.failures).toHaveLength(2);
|
||
expect(result.failures.map((f) => f.key)).toContain("fail1");
|
||
expect(result.failures.map((f) => f.key)).toContain("fail2");
|
||
expect(result.results.ok).toBe("ok");
|
||
expect(result.results.fail1).toBeNull();
|
||
expect(result.results.fail2).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe("全部失败", () => {
|
||
it("所有字段为 null 且 partial=true", async () => {
|
||
const result = await orchestrate({
|
||
a: Promise.reject(new Error("err-a")),
|
||
b: Promise.reject(new Error("err-b")),
|
||
});
|
||
|
||
expect(result.partial).toBe(true);
|
||
expect(result.results.a).toBeNull();
|
||
expect(result.results.b).toBeNull();
|
||
expect(result.failures).toHaveLength(2);
|
||
});
|
||
});
|
||
|
||
describe("空任务", () => {
|
||
it("返回空结果且 partial=false", async () => {
|
||
const result = await orchestrate({});
|
||
|
||
expect(result.partial).toBe(false);
|
||
expect(result.failures).toHaveLength(0);
|
||
expect(Object.keys(result.results)).toHaveLength(0);
|
||
});
|
||
});
|
||
|
||
describe("并行执行", () => {
|
||
it("任务并行执行而非串行", async () => {
|
||
const delay = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||
const start = Date.now();
|
||
|
||
await orchestrate({
|
||
a: delay(100).then(() => "a"),
|
||
b: delay(100).then(() => "b"),
|
||
c: delay(100).then(() => "c"),
|
||
});
|
||
|
||
const elapsed = Date.now() - start;
|
||
// 并行:总耗时约 100ms(而非 300ms)
|
||
expect(elapsed).toBeLessThan(250);
|
||
});
|
||
});
|
||
|
||
describe("failures 结构", () => {
|
||
it("failure 包含 key 和 error", async () => {
|
||
const error = new Error("test error");
|
||
const result = await orchestrate({
|
||
failing: Promise.reject(error),
|
||
});
|
||
|
||
expect(result.failures).toHaveLength(1);
|
||
expect(result.failures[0]!.key).toBe("failing");
|
||
expect(result.failures[0]!.error).toBe(error);
|
||
});
|
||
|
||
it("非 Error 类型的 rejection 也记录", async () => {
|
||
const result = await orchestrate({
|
||
failing: Promise.reject("string error"),
|
||
});
|
||
|
||
expect(result.failures).toHaveLength(1);
|
||
expect(result.failures[0]!.error).toBe("string error");
|
||
});
|
||
});
|
||
});
|