import { describe, expect, it } from "vitest" ;
import { createMetrics, type MetricName } from "./metrics.js" ;
import { validatePrivateKey, isValidPubkey, normalizePubkey } from "./nostr-key-utils.js" ;
import { createSeenTracker } from "./seen-tracker.js" ;
import { TEST_HEX_PRIVATE_KEY } from "./test-fixtures.js" ;
function createTracker(maxEntries = 100 ) {
return createSeenTracker({ maxEntries });
}
function createPlainMetrics() {
return createMetrics();
}
function createCollectingMetrics() {
const events: unknown[] = [];
return {
events,
metrics: createMetrics((event) => events.push(event)),
};
}
// ============================================================================
// Fuzz Tests for validatePrivateKey
// ============================================================================
describe("validatePrivateKey fuzz" , () => {
describe("type confusion" , () => {
it("rejects non-string input" , () => {
for (const value of [null , undefined, 123 , true , {}, [], () => {}]) {
expect(() => validatePrivateKey(value as unknown as string)).toThrow();
}
});
});
describe("unicode attacks" , () => {
it("rejects unicode and control-character attacks" , () => {
const invalidKeys = [
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\u200Bf" ,
`\u202E${TEST_HEX_PRIVATE_KEY}`,
"0123456789\u0430bcdef0123456789abcdef0123456789abcdef0123456789abcdef" ,
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab" ,
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\u0301" ,
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\x00f" ,
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\nf" ,
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\rf" ,
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\tf" ,
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\ff" ,
];
for (const key of invalidKeys) {
expect(() => validatePrivateKey(key)).toThrow();
}
});
});
describe("edge cases" , () => {
it("rejects very long string" , () => {
const veryLong = "a" .repeat(10000 );
expect(() => validatePrivateKey(veryLong)).toThrow();
});
it("rejects string of spaces matching length" , () => {
const spaces = " " .repeat(64 );
expect(() => validatePrivateKey(spaces)).toThrow();
});
it("rejects hex with spaces between characters" , () => {
const withSpaces =
"01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef" ;
expect(() => validatePrivateKey(withSpaces)).toThrow();
});
});
describe("nsec format edge cases" , () => {
it("rejects nsec with invalid bech32 characters" , () => {
// 'b', 'i', 'o' are not valid bech32 characters
const invalidBech32 = "nsec1qypqxpq9qtpqscx7peytbfwtdjmcv0mrz5rjpej8vjppfkqfqy8skqfv3l" ;
expect(() => validatePrivateKey(invalidBech32)).toThrow();
});
it("rejects nsec with wrong prefix" , () => {
expect(() => validatePrivateKey("nsec0aaaa" )).toThrow();
});
it("rejects partial nsec" , () => {
expect(() => validatePrivateKey("nsec1" )).toThrow();
});
});
});
// ============================================================================
// Fuzz Tests for isValidPubkey
// ============================================================================
describe("isValidPubkey fuzz" , () => {
describe("type confusion" , () => {
it("handles non-string input gracefully" , () => {
for (const value of [null , undefined, 123 , {}]) {
expect(isValidPubkey(value as unknown as string)).toBe(false );
}
});
});
describe("malicious inputs" , () => {
it("rejects prototype property names" , () => {
for (const value of ["__proto__" , "constructor" , "toString" ]) {
expect(isValidPubkey(value)).toBe(false );
}
});
});
});
// ============================================================================
// Fuzz Tests for normalizePubkey
// ============================================================================
describe("normalizePubkey fuzz" , () => {
describe("prototype pollution attempts" , () => {
it("throws for prototype property names" , () => {
for (const value of ["__proto__" , "constructor" , "prototype" ]) {
expect(() => normalizePubkey(value)).toThrow();
}
});
});
describe("case sensitivity" , () => {
it("normalizes uppercase to lowercase" , () => {
const upper = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" ;
expect(normalizePubkey(upper)).toBe(TEST_HEX_PRIVATE_KEY);
});
it("normalizes mixed case to lowercase" , () => {
const mixed = "0123456789AbCdEf0123456789AbCdEf0123456789AbCdEf0123456789AbCdEf" ;
expect(normalizePubkey(mixed)).toBe(TEST_HEX_PRIVATE_KEY);
});
});
});
// ============================================================================
// Fuzz Tests for SeenTracker
// ============================================================================
describe("SeenTracker fuzz" , () => {
describe("malformed IDs" , () => {
it("handles empty string IDs" , () => {
const tracker = createTracker();
expect(() => tracker.add("" )).not.toThrow();
expect(tracker.peek("" )).toBe(true );
tracker.stop();
});
it("handles very long IDs" , () => {
const tracker = createTracker();
const longId = "a" .repeat(100000 );
expect(() => tracker.add(longId)).not.toThrow();
expect(tracker.peek(longId)).toBe(true );
tracker.stop();
});
it("handles unicode IDs" , () => {
const tracker = createTracker();
const unicodeId = "事件ID__тест" ;
expect(() => tracker.add(unicodeId)).not.toThrow();
expect(tracker.peek(unicodeId)).toBe(true );
tracker.stop();
});
it("handles IDs with null bytes" , () => {
const tracker = createTracker();
const idWithNull = "event\x00id" ;
expect(() => tracker.add(idWithNull)).not.toThrow();
expect(tracker.peek(idWithNull)).toBe(true );
tracker.stop();
});
it("handles prototype property names as IDs" , () => {
const tracker = createTracker();
// These should not affect the tracker's internal operation
expect(() => tracker.add("__proto__" )).not.toThrow();
expect(() => tracker.add("constructor" )).not.toThrow();
expect(() => tracker.add("toString" )).not.toThrow();
expect(() => tracker.add("hasOwnProperty" )).not.toThrow();
expect(tracker.peek("__proto__" )).toBe(true );
expect(tracker.peek("constructor" )).toBe(true );
expect(tracker.peek("toString" )).toBe(true );
expect(tracker.peek("hasOwnProperty" )).toBe(true );
tracker.stop();
});
});
describe("rapid operations" , () => {
it("handles rapid add/check cycles" , () => {
const tracker = createTracker(1000 );
for (let i = 0 ; i < 10000 ; i++) {
const id = `event-${i}`;
tracker.add(id);
// Recently added should be findable
if (i < 1000 ) {
tracker.peek(id);
}
}
// Size should be capped at maxEntries
expect(tracker.size()).toBeLessThanOrEqual(1000 );
tracker.stop();
});
it("handles concurrent-style operations" , () => {
const tracker = createTracker();
// Simulate interleaved operations
for (let i = 0 ; i < 100 ; i++) {
tracker.add(`add-${i}`);
tracker.peek(`peek-${i}`);
tracker.has(`has-${i}`);
if (i % 10 === 0 ) {
tracker.delete (`add-${i - 5 }`);
}
}
expect(() => tracker.size()).not.toThrow();
tracker.stop();
});
});
describe("seed edge cases" , () => {
it("handles empty seed array" , () => {
const tracker = createTracker();
expect(() => tracker.seed([])).not.toThrow();
expect(tracker.size()).toBe(0 );
tracker.stop();
});
it("handles seed with duplicate IDs" , () => {
const tracker = createTracker();
tracker.seed(["id1" , "id1" , "id1" , "id2" , "id2" ]);
expect(tracker.size()).toBe(2 );
tracker.stop();
});
it("handles seed larger than maxEntries" , () => {
const tracker = createTracker(5 );
const ids = Array.from({ length: 100 }, (_, i) => `id-${i}`);
tracker.seed(ids);
expect(tracker.size()).toBeLessThanOrEqual(5 );
tracker.stop();
});
});
});
// ============================================================================
// Fuzz Tests for Metrics
// ============================================================================
describe("Metrics fuzz" , () => {
describe("invalid metric names" , () => {
it("handles unknown metric names gracefully" , () => {
const metrics = createPlainMetrics();
// Cast to bypass type checking - testing runtime behavior
expect(() => {
metrics.emit("invalid.metric.name" as MetricName);
}).not.toThrow();
});
});
describe("invalid label values" , () => {
it("handles null relay label" , () => {
const metrics = createPlainMetrics();
expect(() => {
metrics.emit("relay.connect" , 1 , { relay: null as unknown as string });
}).not.toThrow();
});
it("handles undefined relay label" , () => {
const metrics = createPlainMetrics();
expect(() => {
metrics.emit("relay.connect" , 1 , { relay: undefined as unknown as string });
}).not.toThrow();
});
it("handles very long relay URL" , () => {
const metrics = createPlainMetrics();
const longUrl = "wss://" + "a".repeat(10000) + ".com";
expect(() => {
metrics.emit("relay.connect" , 1 , { relay: longUrl });
}).not.toThrow();
const snapshot = metrics.getSnapshot();
expect(snapshot.relays[longUrl]).toBeDefined();
});
});
describe("extreme values" , () => {
it("handles NaN value" , () => {
const metrics = createPlainMetrics();
expect(() => metrics.emit("event.received" , Number.NaN)).not.toThrow();
const snapshot = metrics.getSnapshot();
expect(Number.isNaN(snapshot.eventsReceived)).toBe(true );
});
it("handles Infinity value" , () => {
const metrics = createPlainMetrics();
expect(() => metrics.emit("event.received" , Infinity)).not.toThrow();
const snapshot = metrics.getSnapshot();
expect(snapshot.eventsReceived).toBe(Infinity);
});
it("handles negative value" , () => {
const metrics = createPlainMetrics();
metrics.emit("event.received" , -1 );
const snapshot = metrics.getSnapshot();
expect(snapshot.eventsReceived).toBe(-1 );
});
it("handles very large value" , () => {
const metrics = createPlainMetrics();
metrics.emit("event.received" , Number.MAX_SAFE_INTEGER);
const snapshot = metrics.getSnapshot();
expect(snapshot.eventsReceived).toBe(Number.MAX_SAFE_INTEGER);
});
});
describe("rapid emissions" , () => {
it("handles many rapid emissions" , () => {
const { events, metrics } = createCollectingMetrics();
for (let i = 0 ; i < 10000 ; i++) {
metrics.emit("event.received" );
}
expect(events).toHaveLength(10000 );
const snapshot = metrics.getSnapshot();
expect(snapshot.eventsReceived).toBe(10000 );
});
});
describe("reset during operation" , () => {
it("handles reset mid-operation safely" , () => {
const metrics = createPlainMetrics();
metrics.emit("event.received" );
metrics.emit("event.received" );
metrics.reset();
metrics.emit("event.received" );
const snapshot = metrics.getSnapshot();
expect(snapshot.eventsReceived).toBe(1 );
});
});
});
Messung V0.5 in Prozent C=90 H=93 G=91
¤ Dauer der Verarbeitung: 0.9 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland