Skip to content

Commit d995f36

Browse files
author
Execution Coordinator
committed
Merge branch 'fix/issue-357-fetchevent-crash'
2 parents d2b6d6d + 460d3f2 commit d995f36

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
import { NDK } from "./index.js";
3+
4+
describe("fetchEvent with invalid filter (issue #357)", () => {
5+
it("should not crash with ReferenceError when subscribe throws", async () => {
6+
const ndk = new NDK({
7+
explicitRelayUrls: ["wss://relay.example.com"],
8+
});
9+
10+
// Mock subscribe to throw, simulating the scenario where
11+
// an invalid filter causes subscribe to fail before assignment.
12+
// The timeout callback must use optional chaining (s?.stop())
13+
// to avoid crashing on the unassigned variable.
14+
const subscribeSpy = vi.spyOn(ndk, "subscribe").mockImplementation(() => {
15+
throw new Error("Invalid filter caused subscribe to fail");
16+
});
17+
18+
try {
19+
await ndk.fetchEvent({ authors: ["not-a-valid-hex"] });
20+
} catch (error: any) {
21+
// Should get the subscribe error, NOT a ReferenceError about 's'
22+
expect(error).not.toBeInstanceOf(ReferenceError);
23+
expect(error.message).not.toContain("Cannot access");
24+
expect(error.message).toContain("Invalid filter caused subscribe to fail");
25+
}
26+
27+
subscribeSpy.mockRestore();
28+
});
29+
30+
it("should not crash when fetchEvent is called with a malformed hex ID string", async () => {
31+
const ndk = new NDK({
32+
explicitRelayUrls: ["wss://relay.example.com"],
33+
});
34+
35+
// fetchEvent with a bad string should not throw a ReferenceError.
36+
// It may timeout or fail for other reasons, but the 's' variable
37+
// must be safely handled via optional chaining.
38+
let caughtError: Error | undefined;
39+
try {
40+
await Promise.race([
41+
ndk.fetchEvent("zzz-invalid-id"),
42+
new Promise((_, reject) =>
43+
setTimeout(() => reject(new Error("timeout")), 200),
44+
),
45+
]);
46+
} catch (error: any) {
47+
caughtError = error;
48+
}
49+
50+
// Must not be a ReferenceError about 's'
51+
if (caughtError) {
52+
expect(caughtError).not.toBeInstanceOf(ReferenceError);
53+
expect(caughtError.message).not.toContain("Cannot access");
54+
}
55+
});
56+
57+
it("should resolve null when subscribe throws and timeout fires", async () => {
58+
vi.useFakeTimers();
59+
60+
const ndk = new NDK({
61+
explicitRelayUrls: ["wss://relay.example.com"],
62+
});
63+
64+
const subscribeSpy = vi.spyOn(ndk, "subscribe").mockImplementation(() => {
65+
throw new Error("subscribe failed");
66+
});
67+
68+
const promise = ndk.fetchEvent({ ids: ["abc"] });
69+
70+
// The promise should have been rejected immediately by the thrown error
71+
// (Promise constructor catches synchronous throws)
72+
await expect(promise).rejects.toThrow("subscribe failed");
73+
74+
// Advance past the 10-second timeout to ensure it doesn't crash
75+
vi.advanceTimersByTime(11000);
76+
77+
subscribeSpy.mockRestore();
78+
vi.useRealTimers();
79+
});
80+
});

0 commit comments

Comments
 (0)