nostr-mcp-server 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -33
- package/build/__tests__/basic.test.js +2 -2
- package/build/__tests__/integration.test.js +2 -1
- package/build/__tests__/mocks.js +0 -10
- package/build/__tests__/nip19-conversion.test.js +1 -0
- package/build/__tests__/note-creation.test.js +1 -0
- package/build/__tests__/note-tools-functions.test.js +10 -5
- package/build/__tests__/note-tools-unit.test.js +1 -0
- package/build/__tests__/profile-notes-simple.test.js +1 -1
- package/build/__tests__/profile-postnote.test.js +1 -0
- package/build/__tests__/profile-tools.test.js +1 -0
- package/build/__tests__/websocket-integration.test.js +2 -1
- package/build/__tests__/zap-tools-simple.test.js +1 -1
- package/build/__tests__/zap-tools-tests.test.js +1 -0
- package/build/bun.setup.js +3 -0
- package/build/index.js +4 -45
- package/build/zap/zap-tools.js +0 -1
- package/package.json +12 -15
- package/build/__tests__/error-handling.test.js +0 -145
- package/build/__tests__/format-conversion.test.js +0 -137
- package/build/__tests__/nips-search.test.js +0 -109
- package/build/__tests__/relay-specification.test.js +0 -136
- package/build/__tests__/search-nips-simple.test.js +0 -96
- package/build/nips/nips-tools.js +0 -567
- package/build/nips-tools.js +0 -421
- package/build/note-tools.js +0 -53
- package/build/zap-tools.js +0 -989
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from '@jest/globals';
|
|
2
|
-
import * as nip19 from 'nostr-tools/nip19';
|
|
3
|
-
// Import local conversion utilities
|
|
4
|
-
import { npubToHex, hexToNpub } from '../utils/conversion.js';
|
|
5
|
-
describe('Nostr format conversion', () => {
|
|
6
|
-
// Known test vectors - generate these dynamically to ensure they match
|
|
7
|
-
const testHexPubkeys = [
|
|
8
|
-
'3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d',
|
|
9
|
-
'63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed'
|
|
10
|
-
];
|
|
11
|
-
// Generate matching npubs using the library directly
|
|
12
|
-
const knownPairs = testHexPubkeys.map(hex => ({
|
|
13
|
-
hex,
|
|
14
|
-
npub: nip19.npubEncode(hex)
|
|
15
|
-
}));
|
|
16
|
-
// Generate test event IDs and their encoded forms
|
|
17
|
-
const testEventIds = [
|
|
18
|
-
'5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36',
|
|
19
|
-
'9ae37aa68f48645127299e9595f545f942a8314455cbafd9d913ed19c7fc0462'
|
|
20
|
-
];
|
|
21
|
-
const eventIdPairs = testEventIds.map(hex => ({
|
|
22
|
-
hex,
|
|
23
|
-
note: nip19.noteEncode(hex)
|
|
24
|
-
}));
|
|
25
|
-
test('hex to npub conversion', () => {
|
|
26
|
-
knownPairs.forEach(pair => {
|
|
27
|
-
expect(hexToNpub(pair.hex)).toBe(pair.npub);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
test('npub to hex conversion', () => {
|
|
31
|
-
knownPairs.forEach(pair => {
|
|
32
|
-
const result = npubToHex(pair.npub);
|
|
33
|
-
expect(result).toBe(pair.hex);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
test('hex to note conversion using nostr-tools', () => {
|
|
37
|
-
eventIdPairs.forEach(pair => {
|
|
38
|
-
expect(nip19.noteEncode(pair.hex)).toBe(pair.note);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
test('note to hex conversion using nostr-tools', () => {
|
|
42
|
-
eventIdPairs.forEach(pair => {
|
|
43
|
-
const decoded = nip19.decode(pair.note);
|
|
44
|
-
expect(decoded.type).toBe('note');
|
|
45
|
-
expect(decoded.data).toBe(pair.hex);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
test('nevent encoding and decoding using nostr-tools', () => {
|
|
49
|
-
const eventPointer = {
|
|
50
|
-
id: '5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36',
|
|
51
|
-
relays: ['wss://relay.example.com', 'wss://relay.nostr.org'],
|
|
52
|
-
author: '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d',
|
|
53
|
-
kind: 1
|
|
54
|
-
};
|
|
55
|
-
const nevent = nip19.neventEncode(eventPointer);
|
|
56
|
-
expect(nevent).toMatch(/^nevent1/);
|
|
57
|
-
const decoded = nip19.decode(nevent);
|
|
58
|
-
expect(decoded.type).toBe('nevent');
|
|
59
|
-
const data = decoded.data;
|
|
60
|
-
expect(data.id).toBe(eventPointer.id);
|
|
61
|
-
expect(data.author).toBe(eventPointer.author);
|
|
62
|
-
expect(data.kind).toBe(eventPointer.kind);
|
|
63
|
-
expect(data.relays).toEqual(eventPointer.relays);
|
|
64
|
-
});
|
|
65
|
-
test('naddr encoding and decoding using nostr-tools', () => {
|
|
66
|
-
const addressPointer = {
|
|
67
|
-
identifier: 'test-article',
|
|
68
|
-
pubkey: '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d',
|
|
69
|
-
kind: 30023,
|
|
70
|
-
relays: ['wss://relay.example.com']
|
|
71
|
-
};
|
|
72
|
-
const naddr = nip19.naddrEncode(addressPointer);
|
|
73
|
-
expect(naddr).toMatch(/^naddr1/);
|
|
74
|
-
const decoded = nip19.decode(naddr);
|
|
75
|
-
expect(decoded.type).toBe('naddr');
|
|
76
|
-
const data = decoded.data;
|
|
77
|
-
expect(data.identifier).toBe(addressPointer.identifier);
|
|
78
|
-
expect(data.pubkey).toBe(addressPointer.pubkey);
|
|
79
|
-
expect(data.kind).toBe(addressPointer.kind);
|
|
80
|
-
expect(data.relays).toEqual(addressPointer.relays);
|
|
81
|
-
});
|
|
82
|
-
test('validate Nostr identifiers using regex', () => {
|
|
83
|
-
// Valid identifiers
|
|
84
|
-
expect(/^npub1[a-z0-9]{58}$/.test(knownPairs[0].npub)).toBe(true);
|
|
85
|
-
expect(/^note1[a-z0-9]{58}$/.test(eventIdPairs[0].note)).toBe(true);
|
|
86
|
-
// Invalid identifiers with correct regex patterns
|
|
87
|
-
expect(/^npub1[a-z0-9]{58}$/.test('npub1invalid')).toBe(false);
|
|
88
|
-
expect(/^note1[a-z0-9]{58}$/.test('note1invalid')).toBe(false);
|
|
89
|
-
// Fix the regex patterns to be more specific
|
|
90
|
-
expect(/^nevent1[a-z0-9]{58,}$/.test('nevent1invalid')).toBe(false);
|
|
91
|
-
expect(/^naddr1[a-z0-9]{58,}$/.test('naddr1invalid')).toBe(false);
|
|
92
|
-
});
|
|
93
|
-
test('error handling for npub conversion', () => {
|
|
94
|
-
expect(npubToHex('npub1invalid')).toBeNull();
|
|
95
|
-
expect(hexToNpub('invalidhex')).toBeNull();
|
|
96
|
-
});
|
|
97
|
-
test('partial nevent data', () => {
|
|
98
|
-
// Test with minimal data
|
|
99
|
-
const minimalPointer = {
|
|
100
|
-
id: '5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36'
|
|
101
|
-
};
|
|
102
|
-
const nevent = nip19.neventEncode(minimalPointer);
|
|
103
|
-
expect(nevent).toMatch(/^nevent1/);
|
|
104
|
-
const decoded = nip19.decode(nevent);
|
|
105
|
-
expect(decoded.type).toBe('nevent');
|
|
106
|
-
const data = decoded.data;
|
|
107
|
-
expect(data.id).toBe(minimalPointer.id);
|
|
108
|
-
expect(data.relays).toEqual([]);
|
|
109
|
-
expect(data.author).toBeUndefined();
|
|
110
|
-
expect(data.kind).toBeUndefined();
|
|
111
|
-
});
|
|
112
|
-
test('bech32 conversion limits', () => {
|
|
113
|
-
// Test with very long relay list
|
|
114
|
-
const manyRelays = Array(20).fill(0).map((_, i) => `wss://relay${i}.example.com`);
|
|
115
|
-
const eventPointer = {
|
|
116
|
-
id: '5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36',
|
|
117
|
-
relays: manyRelays
|
|
118
|
-
};
|
|
119
|
-
const nevent = nip19.neventEncode(eventPointer);
|
|
120
|
-
expect(nevent).toMatch(/^nevent1/);
|
|
121
|
-
const decoded = nip19.decode(nevent);
|
|
122
|
-
expect(decoded.type).toBe('nevent');
|
|
123
|
-
const data = decoded.data;
|
|
124
|
-
expect(data.id).toBe(eventPointer.id);
|
|
125
|
-
expect(data.relays.length).toBe(manyRelays.length);
|
|
126
|
-
});
|
|
127
|
-
test('edge case: nostr URI handling', () => {
|
|
128
|
-
// Generate a fresh npub
|
|
129
|
-
const hex = '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d';
|
|
130
|
-
const npub = nip19.npubEncode(hex);
|
|
131
|
-
// Test with nostr: prefix
|
|
132
|
-
const nostrUriNpub = 'nostr:' + npub;
|
|
133
|
-
// When handling the nostr: prefix in our conversion functions
|
|
134
|
-
const result = npubToHex(nostrUriNpub.replace('nostr:', ''));
|
|
135
|
-
expect(result).toBe(hex);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, jest } from '@jest/globals';
|
|
2
|
-
// Mock NIP data for testing
|
|
3
|
-
const mockNips = [
|
|
4
|
-
{
|
|
5
|
-
id: '01',
|
|
6
|
-
title: 'Basic Protocol Flow Description',
|
|
7
|
-
content: 'This NIP defines the basic protocol flow between clients and relays...',
|
|
8
|
-
keywords: ['protocol', 'relay', 'client', 'basic']
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
id: '19',
|
|
12
|
-
title: 'bech32-encoded entities',
|
|
13
|
-
content: 'This NIP defines how to encode and reference different Nostr entities...',
|
|
14
|
-
keywords: ['encoding', 'bech32', 'npub', 'note', 'nevent', 'naddr', 'identifier']
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
id: '57',
|
|
18
|
-
title: 'Lightning Zaps',
|
|
19
|
-
content: 'This NIP defines how users can send zaps to each other using lightning...',
|
|
20
|
-
keywords: ['lightning', 'zap', 'payment', 'tip', 'lnurl']
|
|
21
|
-
}
|
|
22
|
-
];
|
|
23
|
-
// Mock data for testing
|
|
24
|
-
const mockSearchResults = (query, limit = 10, includeContent = false) => {
|
|
25
|
-
// Simple relevance scoring algorithm for testing
|
|
26
|
-
const results = mockNips.map(nip => {
|
|
27
|
-
const titleMatches = (nip.title.toLowerCase().includes(query.toLowerCase()) ? 2 : 0);
|
|
28
|
-
const keywordMatches = nip.keywords.filter(kw => kw.includes(query.toLowerCase())).length;
|
|
29
|
-
const contentMatches = (nip.content.toLowerCase().includes(query.toLowerCase()) ? 1 : 0);
|
|
30
|
-
const relevance = titleMatches + keywordMatches + contentMatches;
|
|
31
|
-
return {
|
|
32
|
-
id: nip.id,
|
|
33
|
-
title: nip.title,
|
|
34
|
-
relevance: relevance,
|
|
35
|
-
content: includeContent ? nip.content : undefined
|
|
36
|
-
};
|
|
37
|
-
})
|
|
38
|
-
.filter(result => result.relevance > 0)
|
|
39
|
-
.sort((a, b) => b.relevance - a.relevance)
|
|
40
|
-
.slice(0, limit);
|
|
41
|
-
return results;
|
|
42
|
-
};
|
|
43
|
-
// Mock search function
|
|
44
|
-
const searchNips = jest.fn((query, options = {}) => {
|
|
45
|
-
const { limit = 10, includeContent = false } = options;
|
|
46
|
-
return Promise.resolve(mockSearchResults(query, limit, includeContent));
|
|
47
|
-
});
|
|
48
|
-
describe('NIP Search Functionality', () => {
|
|
49
|
-
beforeEach(() => {
|
|
50
|
-
searchNips.mockClear();
|
|
51
|
-
});
|
|
52
|
-
test('basic search returns relevant results', async () => {
|
|
53
|
-
const results = await searchNips('lightning');
|
|
54
|
-
expect(results.length).toBeGreaterThan(0);
|
|
55
|
-
expect(results[0].id).toBe('57'); // NIP-57 is most relevant for 'lightning'
|
|
56
|
-
expect(results[0].title).toBe('Lightning Zaps');
|
|
57
|
-
expect(results[0].relevance).toBeGreaterThan(0);
|
|
58
|
-
expect(results[0].content).toBeUndefined(); // Content shouldn't be included by default
|
|
59
|
-
});
|
|
60
|
-
test('search with includeContent option', async () => {
|
|
61
|
-
const results = await searchNips('protocol', { includeContent: true });
|
|
62
|
-
expect(results.length).toBeGreaterThan(0);
|
|
63
|
-
expect(results[0].id).toBe('01'); // NIP-01 is most relevant for 'protocol'
|
|
64
|
-
expect(results[0].content).toBeDefined();
|
|
65
|
-
expect(results[0].content).toContain('protocol flow');
|
|
66
|
-
});
|
|
67
|
-
test('search respects limit parameter', async () => {
|
|
68
|
-
// Create a search term that matches multiple NIPs
|
|
69
|
-
const results = await searchNips('n', { limit: 2 });
|
|
70
|
-
// Should return only two results even though more match
|
|
71
|
-
expect(results.length).toBe(2);
|
|
72
|
-
});
|
|
73
|
-
test('search with no matches returns empty array', async () => {
|
|
74
|
-
const results = await searchNips('nonexistentterm');
|
|
75
|
-
expect(results).toEqual([]);
|
|
76
|
-
});
|
|
77
|
-
test('search relevance sorting', async () => {
|
|
78
|
-
// 'encoding' appears in both NIP-19 title/keywords and NIP-57 content, but NIP-19 should rank higher
|
|
79
|
-
const results = await searchNips('encoding');
|
|
80
|
-
expect(results.length).toBeGreaterThan(0);
|
|
81
|
-
expect(results[0].id).toBe('19');
|
|
82
|
-
});
|
|
83
|
-
test('search is case insensitive', async () => {
|
|
84
|
-
const lowerResults = await searchNips('lightning');
|
|
85
|
-
const upperResults = await searchNips('LIGHTNING');
|
|
86
|
-
const mixedResults = await searchNips('LiGhTnInG');
|
|
87
|
-
expect(lowerResults.length).toBeGreaterThan(0);
|
|
88
|
-
expect(upperResults.length).toBeGreaterThan(0);
|
|
89
|
-
expect(mixedResults.length).toBeGreaterThan(0);
|
|
90
|
-
// All should find the same results
|
|
91
|
-
expect(lowerResults[0].id).toBe(upperResults[0].id);
|
|
92
|
-
expect(lowerResults[0].id).toBe(mixedResults[0].id);
|
|
93
|
-
});
|
|
94
|
-
test('search works with partial word matches', async () => {
|
|
95
|
-
const results = await searchNips('light');
|
|
96
|
-
expect(results.length).toBeGreaterThan(0);
|
|
97
|
-
expect(results[0].id).toBe('57'); // Should find 'lightning' in NIP-57
|
|
98
|
-
});
|
|
99
|
-
test('very large limit is handled gracefully', async () => {
|
|
100
|
-
// Even with a large limit, should only return matches
|
|
101
|
-
const results = await searchNips('protocol', { limit: 1000 });
|
|
102
|
-
// Should not break or return more than the available matches
|
|
103
|
-
expect(results.length).toBeLessThanOrEqual(mockNips.length);
|
|
104
|
-
});
|
|
105
|
-
test('zero limit returns empty array', async () => {
|
|
106
|
-
const results = await searchNips('protocol', { limit: 0 });
|
|
107
|
-
expect(results).toEqual([]);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, jest, beforeEach } from '@jest/globals';
|
|
2
|
-
import { NostrRelay } from '../utils/ephemeral-relay.js';
|
|
3
|
-
// Mock the getRelayPool function
|
|
4
|
-
const mockGetRelayPool = jest.fn();
|
|
5
|
-
// Define a mock implementation
|
|
6
|
-
const createMockPool = (relays) => {
|
|
7
|
-
return {
|
|
8
|
-
connect: jest.fn(),
|
|
9
|
-
close: jest.fn(),
|
|
10
|
-
relayUrls: relays,
|
|
11
|
-
subscribeMany: jest.fn().mockImplementation(() => {
|
|
12
|
-
return {
|
|
13
|
-
on: jest.fn(),
|
|
14
|
-
off: jest.fn()
|
|
15
|
-
};
|
|
16
|
-
}),
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
// Set up the mock implementation
|
|
20
|
-
mockGetRelayPool.mockImplementation((relays) => createMockPool(relays || []));
|
|
21
|
-
// Mock the entire module
|
|
22
|
-
jest.mock('../utils/pool.js', () => {
|
|
23
|
-
return {
|
|
24
|
-
getRelayPool: (relays) => mockGetRelayPool(relays)
|
|
25
|
-
};
|
|
26
|
-
});
|
|
27
|
-
// Create a helper to simulate fetching profile data
|
|
28
|
-
const fetchProfileWithRelays = (pubkey, customRelays) => {
|
|
29
|
-
// This simulates how the actual code would use the pool
|
|
30
|
-
const relayPool = mockGetRelayPool(customRelays);
|
|
31
|
-
// Track if we've connected to the pool
|
|
32
|
-
relayPool.connect();
|
|
33
|
-
// Simulate the subscription
|
|
34
|
-
const sub = relayPool.subscribeMany([{ kinds: [0], authors: [pubkey] }]);
|
|
35
|
-
// Return the relays that were used
|
|
36
|
-
return {
|
|
37
|
-
relaysUsed: relayPool.relayUrls,
|
|
38
|
-
pool: relayPool
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
// Define default relays for comparison
|
|
42
|
-
const DEFAULT_RELAYS = [
|
|
43
|
-
'wss://relay.damus.io',
|
|
44
|
-
'wss://relay.nostr.band',
|
|
45
|
-
'wss://relay.primal.net',
|
|
46
|
-
'wss://nos.lol',
|
|
47
|
-
'wss://relay.current.fyi',
|
|
48
|
-
'wss://nostr.bitcoiner.social'
|
|
49
|
-
];
|
|
50
|
-
describe('Custom Relay Specification', () => {
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
// Clear all mocks before each test
|
|
53
|
-
jest.clearAllMocks();
|
|
54
|
-
// Set the default mockGetRelayPool implementation to use DEFAULT_RELAYS when no custom relays
|
|
55
|
-
mockGetRelayPool.mockImplementation((relays) => {
|
|
56
|
-
const trackedRelays = relays && relays.length > 0 ? relays : DEFAULT_RELAYS;
|
|
57
|
-
return createMockPool(trackedRelays);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
test('uses default relays when no custom relays specified', () => {
|
|
61
|
-
const { relaysUsed } = fetchProfileWithRelays('test-pubkey');
|
|
62
|
-
// When no custom relays are specified, should use defaults
|
|
63
|
-
expect(relaysUsed).toEqual(DEFAULT_RELAYS);
|
|
64
|
-
});
|
|
65
|
-
test('uses custom relays when specified', () => {
|
|
66
|
-
const customRelays = ['wss://custom1.example.com', 'wss://custom2.example.com'];
|
|
67
|
-
const { relaysUsed } = fetchProfileWithRelays('test-pubkey', customRelays);
|
|
68
|
-
// Should use exactly the custom relays
|
|
69
|
-
expect(relaysUsed).toEqual(customRelays);
|
|
70
|
-
expect(relaysUsed).not.toEqual(DEFAULT_RELAYS);
|
|
71
|
-
});
|
|
72
|
-
test('empty custom relays array falls back to defaults', () => {
|
|
73
|
-
const { relaysUsed } = fetchProfileWithRelays('test-pubkey', []);
|
|
74
|
-
// When empty array is provided, should fall back to defaults
|
|
75
|
-
expect(relaysUsed).toEqual(DEFAULT_RELAYS);
|
|
76
|
-
});
|
|
77
|
-
test('maintains relay connection pool during operation', () => {
|
|
78
|
-
const customRelays = ['wss://custom1.example.com', 'wss://custom2.example.com'];
|
|
79
|
-
const { pool } = fetchProfileWithRelays('test-pubkey', customRelays);
|
|
80
|
-
// The connect method should be called
|
|
81
|
-
expect(pool.connect).toHaveBeenCalled();
|
|
82
|
-
// The subscribeMany method should be called with appropriate filters
|
|
83
|
-
expect(pool.subscribeMany).toHaveBeenCalledWith(expect.arrayContaining([
|
|
84
|
-
expect.objectContaining({
|
|
85
|
-
kinds: expect.arrayContaining([0]),
|
|
86
|
-
authors: expect.arrayContaining(['test-pubkey'])
|
|
87
|
-
})
|
|
88
|
-
]));
|
|
89
|
-
});
|
|
90
|
-
test('invalid relay URLs are filtered out', () => {
|
|
91
|
-
// Create a special mock implementation for this test
|
|
92
|
-
mockGetRelayPool.mockImplementationOnce((relays) => {
|
|
93
|
-
// Filter invalid URLs (this simulates what the real implementation should do)
|
|
94
|
-
const filteredRelays = relays ? relays.filter((url) => url.startsWith('wss://') && url.includes('.')) : [];
|
|
95
|
-
return createMockPool(filteredRelays);
|
|
96
|
-
});
|
|
97
|
-
// Include some invalid relay URLs
|
|
98
|
-
const mixedRelays = [
|
|
99
|
-
'wss://valid.example.com',
|
|
100
|
-
'invalid-url',
|
|
101
|
-
'http://not-secure.example.com', // Not WSS
|
|
102
|
-
'wss://another-valid.example.com'
|
|
103
|
-
];
|
|
104
|
-
const { relaysUsed } = fetchProfileWithRelays('test-pubkey', mixedRelays);
|
|
105
|
-
// Only valid WSS URLs should remain
|
|
106
|
-
expect(relaysUsed).toContain('wss://valid.example.com');
|
|
107
|
-
expect(relaysUsed).toContain('wss://another-valid.example.com');
|
|
108
|
-
expect(relaysUsed).not.toContain('invalid-url');
|
|
109
|
-
expect(relaysUsed).not.toContain('http://not-secure.example.com');
|
|
110
|
-
});
|
|
111
|
-
test('relay fallback behavior with ephemeral relay', async () => {
|
|
112
|
-
// Create an ephemeral relay for testing - using port 9000 for test
|
|
113
|
-
const relay = new NostrRelay(9000);
|
|
114
|
-
// Create a request that specifies two relays:
|
|
115
|
-
// 1. A non-existent relay that will fail
|
|
116
|
-
// 2. Our ephemeral relay that will work
|
|
117
|
-
const testRelays = [
|
|
118
|
-
'wss://non-existent-relay.example.com',
|
|
119
|
-
relay.url
|
|
120
|
-
];
|
|
121
|
-
// This is a more advanced test that would require actual implementation
|
|
122
|
-
// of relay fallback logic. We'll outline the concept here.
|
|
123
|
-
// In a real test with actual implementation:
|
|
124
|
-
// 1. Send a request to the specified relays
|
|
125
|
-
// 2. The first relay fails to connect
|
|
126
|
-
// 3. The code falls back to the second relay
|
|
127
|
-
// 4. We get a successful response from the second relay
|
|
128
|
-
// For now, we'll just verify that both relays are attempted
|
|
129
|
-
const { relaysUsed } = fetchProfileWithRelays('test-pubkey', testRelays);
|
|
130
|
-
expect(relaysUsed).toEqual(testRelays);
|
|
131
|
-
// In a full implementation, we would:
|
|
132
|
-
// 1. Set up a callback for events from the ephemeral relay
|
|
133
|
-
// 2. Publish a test profile to the ephemeral relay
|
|
134
|
-
// 3. Verify we receive it even though the first relay fails
|
|
135
|
-
});
|
|
136
|
-
});
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { searchNips } from '../nips/nips-tools.js';
|
|
2
|
-
// Since the NIPs tool uses a real cache, we'll test with the actual cache behavior
|
|
3
|
-
describe('Search NIPs Tool - Simple Tests', () => {
|
|
4
|
-
describe('basic search functionality', () => {
|
|
5
|
-
it('should search NIPs by keyword', async () => {
|
|
6
|
-
// This will use the real cache if available, or fetch from GitHub
|
|
7
|
-
const result = await searchNips('protocol', 5);
|
|
8
|
-
expect(Array.isArray(result)).toBe(true);
|
|
9
|
-
// Should find NIP-01 when searching for "protocol"
|
|
10
|
-
const nip01 = result.find(searchResult => searchResult.nip.number === 1);
|
|
11
|
-
if (nip01) {
|
|
12
|
-
expect(nip01.nip.title.toLowerCase()).toContain('protocol');
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
it('should return limited results', async () => {
|
|
16
|
-
const limit = 3;
|
|
17
|
-
const result = await searchNips('event', limit);
|
|
18
|
-
expect(Array.isArray(result)).toBe(true);
|
|
19
|
-
expect(result.length).toBeLessThanOrEqual(limit);
|
|
20
|
-
});
|
|
21
|
-
it('should return results with correct structure', async () => {
|
|
22
|
-
// Use a more common search term that's likely to return results
|
|
23
|
-
const result = await searchNips('event', 5);
|
|
24
|
-
expect(Array.isArray(result)).toBe(true);
|
|
25
|
-
// Each result should have the expected structure
|
|
26
|
-
if (result.length > 0) {
|
|
27
|
-
result.forEach(searchResult => {
|
|
28
|
-
expect(searchResult).toHaveProperty('nip');
|
|
29
|
-
expect(searchResult).toHaveProperty('relevance');
|
|
30
|
-
expect(searchResult).toHaveProperty('matchedTerms');
|
|
31
|
-
// Check the nested nip structure
|
|
32
|
-
expect(searchResult.nip).toHaveProperty('number');
|
|
33
|
-
expect(searchResult.nip).toHaveProperty('title');
|
|
34
|
-
expect(searchResult.nip).toHaveProperty('status');
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
it('should handle no results gracefully', async () => {
|
|
39
|
-
const result = await searchNips('xyz123nonexistentterm456', 10);
|
|
40
|
-
expect(Array.isArray(result)).toBe(true);
|
|
41
|
-
expect(result).toEqual([]);
|
|
42
|
-
});
|
|
43
|
-
it('should handle case-insensitive search', async () => {
|
|
44
|
-
const result1 = await searchNips('PROTOCOL', 5);
|
|
45
|
-
const result2 = await searchNips('protocol', 5);
|
|
46
|
-
expect(Array.isArray(result1)).toBe(true);
|
|
47
|
-
expect(Array.isArray(result2)).toBe(true);
|
|
48
|
-
// Both searches should return results
|
|
49
|
-
if (result1.length > 0 && result2.length > 0) {
|
|
50
|
-
// The results should be similar (may not be identical due to scoring)
|
|
51
|
-
expect(result1.length).toBeGreaterThan(0);
|
|
52
|
-
expect(result2.length).toBeGreaterThan(0);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
describe('search relevance', () => {
|
|
57
|
-
it('should return relevant results for common terms', async () => {
|
|
58
|
-
// Test with terms we know exist in NIPs
|
|
59
|
-
const result = await searchNips('event', 5);
|
|
60
|
-
expect(Array.isArray(result)).toBe(true);
|
|
61
|
-
if (result.length > 0) {
|
|
62
|
-
// At least one result should have a relevance score
|
|
63
|
-
const hasRelevantResult = result.some(searchResult => searchResult.relevance > 0);
|
|
64
|
-
expect(hasRelevantResult).toBe(true);
|
|
65
|
-
// Results should be sorted by relevance score
|
|
66
|
-
expect(result[0].relevance).toBeGreaterThan(0);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
it('should rank results by relevance score', async () => {
|
|
70
|
-
const result = await searchNips('encryption', 10);
|
|
71
|
-
expect(Array.isArray(result)).toBe(true);
|
|
72
|
-
if (result.length > 1) {
|
|
73
|
-
// Results should be sorted by relevance score (descending)
|
|
74
|
-
for (let i = 1; i < result.length; i++) {
|
|
75
|
-
if (result[i - 1].relevance !== undefined && result[i].relevance !== undefined) {
|
|
76
|
-
expect(result[i - 1].relevance).toBeGreaterThanOrEqual(result[i].relevance);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
describe('error handling', () => {
|
|
83
|
-
it('should handle empty search query', async () => {
|
|
84
|
-
const result = await searchNips('', 10);
|
|
85
|
-
// Empty query should return empty array
|
|
86
|
-
expect(Array.isArray(result)).toBe(true);
|
|
87
|
-
expect(result).toEqual([]);
|
|
88
|
-
});
|
|
89
|
-
it('should handle very long search queries gracefully', async () => {
|
|
90
|
-
const longQuery = 'a'.repeat(1000);
|
|
91
|
-
const result = await searchNips(longQuery, 10);
|
|
92
|
-
// Should return an array (possibly empty)
|
|
93
|
-
expect(Array.isArray(result)).toBe(true);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
});
|