add support heartbeat to couchdb-changes-stream

This commit is contained in:
2024-11-29 12:38:08 +02:00
parent a393a195d0
commit 9cc03623d9
27 changed files with 8998 additions and 8531 deletions
@@ -0,0 +1,133 @@
import {
CouchDBChangesStream,
CouchDBChange,
} from "../src/CouchDBChangesStream";
describe("CouchDBChangesStream - Heartbeat", () => {
let mockFetch: jest.Mock;
const dbUrl = "http://localhost:5984/mydb";
beforeEach(() => {
mockFetch = jest.fn();
jest.useFakeTimers(); // Подключаем fake timers
});
afterEach(() => {
jest.useRealTimers(); // Отключаем fake timers после каждого теста
jest.restoreAllMocks();
});
it("should reset heartbeat timer on receiving data", async () => {
const mockResponseData = {
seq: "1",
id: "doc1",
changes: [{ rev: "1-abc" }],
};
mockFetch.mockResolvedValueOnce({
ok: true,
body: new ReadableStream({
start(controller) {
controller.enqueue(
new TextEncoder().encode(JSON.stringify(mockResponseData) + "\n"),
);
controller.close();
},
}),
});
const stream = new CouchDBChangesStream(dbUrl, {
feed: "continuous",
heartbeat: 30_000, // 30 секунд
live: true,
fetch: mockFetch,
});
const iterator = stream[Symbol.asyncIterator]();
const heartbeatSpy = jest.spyOn(global, "setTimeout");
const firstChange = await iterator.next();
expect(firstChange.done).toBe(false);
expect(firstChange.value.seq).toBe("1");
// Таймер heartbeat должен был быть установлен
expect(heartbeatSpy).toHaveBeenCalledWith(expect.any(Function), 33_000); // 5000 + 10% буфер,
});
it("should throw an error if no data is received within heartbeat + buffer", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
body: new ReadableStream({
start(controller) {
// do not send any data to test heartbeat timeout
},
}),
});
const stream = new CouchDBChangesStream(dbUrl, {
feed: "continuous",
heartbeat: 20_000,
fetch: mockFetch,
});
const iterator = stream[Symbol.asyncIterator]();
const fetchPromise = iterator.next();
// Прокручиваем время до истечения heartbeat таймера (20_000 + 10%)
jest.advanceTimersByTime(22_000);
await expect(fetchPromise).rejects.toThrow(
"Heartbeat timeout: no data received from server after 22000 ms",
);
});
it("should continue working if data is received before heartbeat timeout", async () => {
const mockResponseData = {
seq: "1",
id: "doc1",
changes: [{ rev: "1-abc" }],
};
mockFetch.mockResolvedValue({
ok: true,
body: new ReadableStream({
start(controller) {
// Отправляем данные перед истечением таймера
setTimeout(() => {
controller.enqueue(
new TextEncoder().encode(JSON.stringify(mockResponseData) + "\n"),
);
controller.close();
}, 2000); // Data arrives before heartbeat timeout (3000)
},
}),
});
const stream = new CouchDBChangesStream(dbUrl, {
feed: "continuous",
heartbeat: 3_000,
fetch: mockFetch,
live: true,
});
const iterator = stream[Symbol.asyncIterator]();
const nextPromise = iterator.next();
await Promise.resolve();
jest.advanceTimersByTime(2_999);
await Promise.resolve();
const firstChange = await nextPromise;
expect(firstChange.done).toBe(false);
expect(firstChange.value.seq).toBe("1");
// Let's make sure that a timeout error does not occur
await Promise.resolve();
jest.advanceTimersByTime(2_999);
await Promise.resolve();
const secondFetch = iterator.next();
await expect(secondFetch).resolves.not.toThrow();
});
});