feat(push-gateway): config 扩展 + kafka consumer + ws handler + nextstep 文档
This commit is contained in:
98
services/push-gateway/scripts/test-exam-events.mjs
Normal file
98
services/push-gateway/scripts/test-exam-events.mjs
Normal file
@@ -0,0 +1,98 @@
|
||||
// Test script: Exam events through Kafka → push-gateway → WebSocket (v2)
|
||||
// Verifies that the 3 exam events (ExamExtended, ExamForceSubmitted,
|
||||
// ExamQuestionReordered) traverse the new edu.notify.notification.sent topic
|
||||
// and reach the WebSocket client with correct event_type passthrough.
|
||||
//
|
||||
// Usage: node services/push-gateway/scripts/test-exam-events.mjs
|
||||
|
||||
import WebSocket from "ws";
|
||||
import { Kafka } from "kafkajs";
|
||||
|
||||
const WS_URL = "ws://localhost:8081/ws?token=dev-token";
|
||||
const KAFKA_BROKER = "localhost:9092";
|
||||
const TOPIC = "edu.notify.notification.sent";
|
||||
const USER_ID = "dev-user";
|
||||
|
||||
const examEvents = [
|
||||
{
|
||||
event_type: "ExamExtended",
|
||||
data: { examId: "exam-001", extendedMinutes: 15, reason: "special needs" },
|
||||
},
|
||||
{
|
||||
event_type: "ExamForceSubmitted",
|
||||
data: { examId: "exam-001", reason: "time_expired", submissionId: "sub-001" },
|
||||
},
|
||||
{
|
||||
event_type: "ExamQuestionReordered",
|
||||
data: { examId: "exam-001", questionIds: ["q-3", "q-1", "q-2"] },
|
||||
},
|
||||
];
|
||||
|
||||
console.log("[exam-test] connecting WebSocket to", WS_URL);
|
||||
const ws = new WebSocket(WS_URL);
|
||||
const received = [];
|
||||
|
||||
ws.on("open", async () => {
|
||||
console.log("[exam-test] WebSocket connected, publishing 3 exam events...");
|
||||
const kafka = new Kafka({ brokers: [KAFKA_BROKER] });
|
||||
const producer = kafka.producer();
|
||||
await producer.connect();
|
||||
|
||||
for (const ev of examEvents) {
|
||||
const payload = {
|
||||
event_id: `evt-exam-${ev.event_type}-${Date.now()}`,
|
||||
user_id: USER_ID,
|
||||
event_type: ev.event_type,
|
||||
channel: "ws",
|
||||
title: `Exam event: ${ev.event_type}`,
|
||||
content: `Verification of ${ev.event_type} passthrough`,
|
||||
data: ev.data,
|
||||
broadcast: false,
|
||||
occurred_at: Date.now(),
|
||||
};
|
||||
await producer.send({
|
||||
topic: TOPIC,
|
||||
messages: [{ value: JSON.stringify(payload) }],
|
||||
});
|
||||
console.log(`[exam-test] published ${ev.event_type}`);
|
||||
}
|
||||
await producer.disconnect();
|
||||
|
||||
// Wait up to 15 seconds for all 3 messages.
|
||||
const deadline = Date.now() + 15000;
|
||||
while (Date.now() < deadline && received.length < 3) {
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
}
|
||||
|
||||
if (received.length < 3) {
|
||||
console.error(`[exam-test] FAIL: only ${received.length}/3 messages received`);
|
||||
process.exit(1);
|
||||
}
|
||||
const got = received.map((m) => m.event).sort();
|
||||
const want = examEvents.map((e) => e.event_type).sort();
|
||||
if (JSON.stringify(got) === JSON.stringify(want)) {
|
||||
console.log("[exam-test] PASS: all 3 exam events received with correct event_type");
|
||||
console.log("[exam-test] events:", got.join(", "));
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.error("[exam-test] FAIL: event mismatch");
|
||||
console.error(" got:", got);
|
||||
console.error(" want:", want);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("message", (data) => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
console.log("[exam-test] received:", JSON.stringify(msg));
|
||||
received.push(msg);
|
||||
});
|
||||
ws.on("error", (err) => {
|
||||
console.error("[exam-test] WebSocket error:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.error("[exam-test] FAIL: overall timeout 20s");
|
||||
process.exit(1);
|
||||
}, 20000);
|
||||
78
services/push-gateway/scripts/test-kafka-ws.mjs
Normal file
78
services/push-gateway/scripts/test-kafka-ws.mjs
Normal file
@@ -0,0 +1,78 @@
|
||||
// Test script: Kafka → push-gateway → WebSocket full chain (v2)
|
||||
// Usage: node scripts/test-kafka-ws.mjs
|
||||
// Prerequisites: push-gateway running on localhost:8081, Kafka on localhost:9092,
|
||||
// topic edu.notify.notification.sent exists.
|
||||
|
||||
import WebSocket from "ws";
|
||||
import { Kafka } from "kafkajs";
|
||||
|
||||
const WS_URL = "ws://localhost:8081/ws?token=dev-token";
|
||||
const KAFKA_BROKER = "localhost:9092";
|
||||
const TOPIC = "edu.notify.notification.sent";
|
||||
const USER_ID = "dev-user"; // DevMode maps dev-token to this user
|
||||
const EVENT_TYPE = "TestEventV2KafkaChain";
|
||||
|
||||
console.log("[test] connecting WebSocket to", WS_URL);
|
||||
const ws = new WebSocket(WS_URL);
|
||||
|
||||
const messages = [];
|
||||
ws.on("open", () => {
|
||||
console.log("[test] WebSocket connected, publishing Kafka message...");
|
||||
publishKafka();
|
||||
});
|
||||
ws.on("message", (data) => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
console.log("[test] received WS message:", JSON.stringify(msg));
|
||||
messages.push(msg);
|
||||
});
|
||||
ws.on("error", (err) => {
|
||||
console.error("[test] WebSocket error:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
async function publishKafka() {
|
||||
const kafka = new Kafka({ brokers: [KAFKA_BROKER] });
|
||||
const producer = kafka.producer();
|
||||
await producer.connect();
|
||||
const payload = {
|
||||
event_id: `evt-test-${Date.now()}`,
|
||||
user_id: USER_ID,
|
||||
event_type: EVENT_TYPE,
|
||||
channel: "ws",
|
||||
title: "v2 Kafka chain test",
|
||||
content: "Verifying edu.notify.notification.sent consumption",
|
||||
data: { message: "hello from v2 kafka chain test" },
|
||||
broadcast: false,
|
||||
occurred_at: Date.now(),
|
||||
};
|
||||
await producer.send({
|
||||
topic: TOPIC,
|
||||
messages: [{ value: JSON.stringify(payload) }],
|
||||
});
|
||||
console.log("[test] Kafka message published to", TOPIC);
|
||||
await producer.disconnect();
|
||||
|
||||
// Wait up to 10 seconds for the WS message.
|
||||
const deadline = Date.now() + 10000;
|
||||
while (Date.now() < deadline && messages.length === 0) {
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
}
|
||||
if (messages.length === 0) {
|
||||
console.error("[test] FAIL: no WS message received within 10s");
|
||||
process.exit(1);
|
||||
}
|
||||
const got = messages[0];
|
||||
if (got.event === EVENT_TYPE && got.type === "message") {
|
||||
console.log("[test] PASS: WS message matches expected event_type");
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.error("[test] FAIL: WS message mismatch:", JSON.stringify(got));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Hard timeout.
|
||||
setTimeout(() => {
|
||||
console.error("[test] FAIL: overall timeout 15s");
|
||||
process.exit(1);
|
||||
}, 15000);
|
||||
Reference in New Issue
Block a user