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
111 lines
3.0 KiB
TypeScript
111 lines
3.0 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
||
import {
|
||
UpdateNotificationPreferencesSchema,
|
||
NotificationChannelSchema,
|
||
NotificationEventTypesInputSchema,
|
||
} from "../../src/parent/dto/parent-inputs.dto.js";
|
||
|
||
describe("NotificationChannelSchema", () => {
|
||
it("接受有效渠道", () => {
|
||
expect(NotificationChannelSchema.parse("APP")).toBe("APP");
|
||
expect(NotificationChannelSchema.parse("SMS")).toBe("SMS");
|
||
expect(NotificationChannelSchema.parse("EMAIL")).toBe("EMAIL");
|
||
expect(NotificationChannelSchema.parse("WECHAT")).toBe("WECHAT");
|
||
});
|
||
|
||
it("拒绝无效渠道", () => {
|
||
expect(() => NotificationChannelSchema.parse("PUSH")).toThrow();
|
||
expect(() => NotificationChannelSchema.parse("")).toThrow();
|
||
});
|
||
});
|
||
|
||
describe("NotificationEventTypesInputSchema", () => {
|
||
it("接受所有可选布尔字段", () => {
|
||
const result = NotificationEventTypesInputSchema.parse({
|
||
gradeReleased: true,
|
||
homeworkGraded: false,
|
||
examPublished: true,
|
||
attendanceAlert: false,
|
||
schoolAnnouncement: true,
|
||
});
|
||
expect(result.gradeReleased).toBe(true);
|
||
expect(result.homeworkGraded).toBe(false);
|
||
});
|
||
|
||
it("允许部分字段(未传为 undefined)", () => {
|
||
const result = NotificationEventTypesInputSchema.parse({
|
||
gradeReleased: true,
|
||
});
|
||
expect(result.gradeReleased).toBe(true);
|
||
expect(result.homeworkGraded).toBeUndefined();
|
||
});
|
||
|
||
it("接受空对象", () => {
|
||
const result = NotificationEventTypesInputSchema.parse({});
|
||
expect(result.gradeReleased).toBeUndefined();
|
||
});
|
||
|
||
it("拒绝非布尔值", () => {
|
||
expect(() =>
|
||
NotificationEventTypesInputSchema.parse({ gradeReleased: "yes" }),
|
||
).toThrow();
|
||
});
|
||
});
|
||
|
||
describe("UpdateNotificationPreferencesSchema", () => {
|
||
it("接受完整有效输入", () => {
|
||
const input = {
|
||
channels: ["APP", "WECHAT"],
|
||
eventTypes: {
|
||
gradeReleased: true,
|
||
homeworkGraded: false,
|
||
},
|
||
};
|
||
const result = UpdateNotificationPreferencesSchema.parse(input);
|
||
expect(result.channels).toEqual(["APP", "WECHAT"]);
|
||
expect(result.eventTypes.gradeReleased).toBe(true);
|
||
});
|
||
|
||
it("拒绝空 channels 数组", () => {
|
||
expect(() =>
|
||
UpdateNotificationPreferencesSchema.parse({
|
||
channels: [],
|
||
eventTypes: {},
|
||
}),
|
||
).toThrow();
|
||
});
|
||
|
||
it("拒绝无效 channel 值", () => {
|
||
expect(() =>
|
||
UpdateNotificationPreferencesSchema.parse({
|
||
channels: ["INVALID"],
|
||
eventTypes: {},
|
||
}),
|
||
).toThrow();
|
||
});
|
||
|
||
it("拒绝缺少 channels", () => {
|
||
expect(() =>
|
||
UpdateNotificationPreferencesSchema.parse({
|
||
eventTypes: {},
|
||
}),
|
||
).toThrow();
|
||
});
|
||
|
||
it("拒绝缺少 eventTypes", () => {
|
||
expect(() =>
|
||
UpdateNotificationPreferencesSchema.parse({
|
||
channels: ["APP"],
|
||
}),
|
||
).toThrow();
|
||
});
|
||
|
||
it("接受单个 channel", () => {
|
||
const result = UpdateNotificationPreferencesSchema.parse({
|
||
channels: ["APP"],
|
||
eventTypes: {},
|
||
});
|
||
expect(result.channels).toEqual(["APP"]);
|
||
});
|
||
});
|