mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
feat(test): Add DiscussionCountService error handling tests
Add comprehensive tests for `DiscussionCountService` focusing on error handling, input validation, and caching. - Covers happy path, partial results, and unsupported relays. - Validates graceful degradation when `nostrClient` calls fail. - Ensures no regression in service functionality. Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
566c2dbf89
commit
1ac9fee2ed
1 file changed
+192
@@ -0,0 +1,192 @@
|
||||
import { describe, test, mock, before, after, beforeEach } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createUiDom } from "../ui/helpers/jsdom-test-helpers.mjs";
|
||||
import DiscussionCountService from "../../js/services/discussionCountService.js";
|
||||
|
||||
describe("DiscussionCountService", () => {
|
||||
let dom;
|
||||
let document;
|
||||
let window;
|
||||
|
||||
before(() => {
|
||||
dom = createUiDom();
|
||||
window = dom.window;
|
||||
document = dom.document;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
dom.cleanup();
|
||||
});
|
||||
|
||||
test("initialization sets defaults", () => {
|
||||
const service = new DiscussionCountService();
|
||||
assert.equal(service.maxVideos, 24);
|
||||
assert.ok(service.logger);
|
||||
assert.equal(service.videoDiscussionCountCache.size, 0);
|
||||
assert.equal(service.inFlightDiscussionCounts.size, 0);
|
||||
});
|
||||
|
||||
test("refreshCounts returns early if videos array is empty or invalid", () => {
|
||||
const service = new DiscussionCountService();
|
||||
// mock logger to ensure no warnings
|
||||
const logger = { warn: mock.fn() };
|
||||
service.logger = logger;
|
||||
|
||||
service.refreshCounts([], {});
|
||||
service.refreshCounts(null, {});
|
||||
|
||||
assert.equal(service.inFlightDiscussionCounts.size, 0);
|
||||
assert.equal(logger.warn.mock.callCount(), 0);
|
||||
});
|
||||
|
||||
test("refreshCounts returns early if dependencies are missing", () => {
|
||||
const service = new DiscussionCountService();
|
||||
const video = { id: "video1" };
|
||||
|
||||
// Missing nostrClient
|
||||
service.refreshCounts([video], { videoListRoot: document.createElement("div") });
|
||||
assert.equal(service.inFlightDiscussionCounts.size, 0);
|
||||
|
||||
// Missing videoListRoot
|
||||
service.refreshCounts([video], { nostrClient: {} });
|
||||
assert.equal(service.inFlightDiscussionCounts.size, 0);
|
||||
});
|
||||
|
||||
test("refreshCounts handles happy path (fetches and updates DOM)", async () => {
|
||||
const service = new DiscussionCountService();
|
||||
const video = { id: "video1", kind: 30078, pubkey: "pubkey1" };
|
||||
|
||||
const root = document.createElement("div");
|
||||
const container = document.createElement("div");
|
||||
container.dataset.discussionCount = "video1";
|
||||
const valueEl = document.createElement("span");
|
||||
valueEl.dataset.discussionCountValue = "true";
|
||||
container.appendChild(valueEl);
|
||||
root.appendChild(container);
|
||||
|
||||
const nostrClient = {
|
||||
pool: {},
|
||||
countEventsAcrossRelays: mock.fn(async () => ({
|
||||
total: 42,
|
||||
ok: true,
|
||||
perRelay: [{ ok: true }]
|
||||
}))
|
||||
};
|
||||
|
||||
service.refreshCounts([video], { videoListRoot: root, nostrClient });
|
||||
|
||||
// Wait for promise resolution (microtasks)
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
assert.equal(nostrClient.countEventsAcrossRelays.mock.callCount(), 1);
|
||||
assert.equal(valueEl.textContent, "42");
|
||||
assert.equal(container.dataset.countState, "ready");
|
||||
assert.equal(service.videoDiscussionCountCache.get("video1"), 42);
|
||||
});
|
||||
|
||||
test("refreshCounts uses cached count if available", async () => {
|
||||
const service = new DiscussionCountService();
|
||||
const video = { id: "video1" };
|
||||
service.videoDiscussionCountCache.set("video1", 100);
|
||||
|
||||
const root = document.createElement("div");
|
||||
const container = document.createElement("div");
|
||||
container.dataset.discussionCount = "video1";
|
||||
const valueEl = document.createElement("span");
|
||||
valueEl.dataset.discussionCountValue = "true";
|
||||
container.appendChild(valueEl);
|
||||
root.appendChild(container);
|
||||
|
||||
const nostrClient = {
|
||||
pool: {},
|
||||
countEventsAcrossRelays: mock.fn()
|
||||
};
|
||||
|
||||
service.refreshCounts([video], { videoListRoot: root, nostrClient });
|
||||
|
||||
assert.equal(nostrClient.countEventsAcrossRelays.mock.callCount(), 0);
|
||||
assert.equal(valueEl.textContent, "100");
|
||||
});
|
||||
|
||||
test("refreshCounts handles API errors gracefully", async () => {
|
||||
const logger = { warn: mock.fn() };
|
||||
const service = new DiscussionCountService({ logger });
|
||||
const video = { id: "video1" };
|
||||
|
||||
const root = document.createElement("div");
|
||||
const container = document.createElement("div");
|
||||
container.dataset.discussionCount = "video1";
|
||||
const valueEl = document.createElement("span");
|
||||
valueEl.dataset.discussionCountValue = "true";
|
||||
container.appendChild(valueEl);
|
||||
root.appendChild(container);
|
||||
|
||||
const error = new Error("Network error");
|
||||
const nostrClient = {
|
||||
pool: {},
|
||||
countEventsAcrossRelays: mock.fn(async () => { throw error; })
|
||||
};
|
||||
|
||||
service.refreshCounts([video], { videoListRoot: root, nostrClient });
|
||||
|
||||
const requestPromise = service.inFlightDiscussionCounts.get("video1");
|
||||
await assert.rejects(requestPromise, error);
|
||||
|
||||
assert.equal(logger.warn.mock.callCount(), 1);
|
||||
assert.equal(container.dataset.countState, "error");
|
||||
assert.equal(valueEl.textContent, "—");
|
||||
});
|
||||
|
||||
test("refreshCounts handles partial results", async () => {
|
||||
const service = new DiscussionCountService();
|
||||
const video = { id: "video1" };
|
||||
|
||||
const root = document.createElement("div");
|
||||
const container = document.createElement("div");
|
||||
container.dataset.discussionCount = "video1";
|
||||
const valueEl = document.createElement("span");
|
||||
valueEl.dataset.discussionCountValue = "true";
|
||||
container.appendChild(valueEl);
|
||||
root.appendChild(container);
|
||||
|
||||
const nostrClient = {
|
||||
pool: {},
|
||||
countEventsAcrossRelays: mock.fn(async () => ({ total: 5, partial: true, ok: true, perRelay: [{ ok: true }] }))
|
||||
};
|
||||
|
||||
service.refreshCounts([video], { videoListRoot: root, nostrClient });
|
||||
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
assert.equal(container.dataset.countState, "partial");
|
||||
assert.equal(valueEl.textContent, "5");
|
||||
});
|
||||
|
||||
test("refreshCounts handles unsupported relays (empty perRelay)", async () => {
|
||||
const service = new DiscussionCountService();
|
||||
const video = { id: "video1" };
|
||||
|
||||
const root = document.createElement("div");
|
||||
const container = document.createElement("div");
|
||||
container.dataset.discussionCount = "video1";
|
||||
const valueEl = document.createElement("span");
|
||||
valueEl.dataset.discussionCountValue = "true";
|
||||
container.appendChild(valueEl);
|
||||
root.appendChild(container);
|
||||
|
||||
const nostrClient = {
|
||||
pool: {},
|
||||
countEventsAcrossRelays: mock.fn(async () => ({ total: 0, perRelay: [] }))
|
||||
};
|
||||
|
||||
service.refreshCounts([video], { videoListRoot: root, nostrClient });
|
||||
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
assert.equal(container.dataset.countState, "unsupported");
|
||||
assert.equal(valueEl.textContent, "—");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user