psf-bch-api 1.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/.env-local +4 -0
- package/LICENSE.md +8 -0
- package/README.md +8 -0
- package/apidoc.json +9 -0
- package/bin/server.js +183 -0
- package/dev-docs/README.md +4 -0
- package/dev-docs/creation-prompt.md +34 -0
- package/dev-docs/rest2nostr-poxy-api.plan.md +163 -0
- package/dev-docs/test-plan-for-rest2nostr.plan.md +161 -0
- package/dev-docs/unit-test-prompt.md +13 -0
- package/examples/01-create-account.js +67 -0
- package/examples/02-read-posts.js +44 -0
- package/examples/03-write-post.js +55 -0
- package/examples/04-read-alice-posts.js +49 -0
- package/examples/05-get-follow-list.js +53 -0
- package/examples/06-update-follow-list.js +63 -0
- package/examples/07-liking-event.js +59 -0
- package/examples/README.md +90 -0
- package/index.js +11 -0
- package/package.json +37 -0
- package/production/docker/Dockerfile +85 -0
- package/production/docker/cleanup-images.sh +5 -0
- package/production/docker/docker-compose.yml +19 -0
- package/production/docker/start-rest2nostr.sh +3 -0
- package/src/adapters/full-node-rpc.js +133 -0
- package/src/adapters/index.js +217 -0
- package/src/adapters/wlogger.js +79 -0
- package/src/config/env/common.js +64 -0
- package/src/config/env/development.js +7 -0
- package/src/config/env/production.js +7 -0
- package/src/config/index.js +14 -0
- package/src/controllers/index.js +56 -0
- package/src/controllers/rest-api/full-node/blockchain/controller.js +553 -0
- package/src/controllers/rest-api/full-node/blockchain/index.js +66 -0
- package/src/controllers/rest-api/index.js +55 -0
- package/src/controllers/timer-controller.js +72 -0
- package/src/entities/event.js +71 -0
- package/src/use-cases/full-node-blockchain-use-cases.js +134 -0
- package/src/use-cases/index.js +29 -0
- package/test/integration/api/event-integration.js +250 -0
- package/test/integration/api/req-integration.js +173 -0
- package/test/integration/api/subscription-integration.js +198 -0
- package/test/integration/use-cases/manage-subscription-integration.js +163 -0
- package/test/integration/use-cases/publish-event-integration.js +104 -0
- package/test/integration/use-cases/query-events-integration.js +95 -0
- package/test/unit/adapters/full-node-rpc-unit.js +122 -0
- package/test/unit/bin/server-unit.js +63 -0
- package/test/unit/controllers/blockchain-controller-unit.js +215 -0
- package/test/unit/controllers/rest-api-index-unit.js +85 -0
- package/test/unit/entities/event-unit.js +139 -0
- package/test/unit/mocks/controller-mocks.js +98 -0
- package/test/unit/mocks/event-mocks.js +194 -0
- package/test/unit/use-cases/full-node-blockchain-use-cases-unit.js +137 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Unit tests for the Event entity.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// npm libraries
|
|
6
|
+
import { assert } from 'chai'
|
|
7
|
+
|
|
8
|
+
// Mocking data libraries
|
|
9
|
+
import {
|
|
10
|
+
mockKind0Event,
|
|
11
|
+
mockKind1Event,
|
|
12
|
+
mockKind3Event,
|
|
13
|
+
mockKind7Event,
|
|
14
|
+
mockInvalidEventMissingId,
|
|
15
|
+
mockInvalidEventWrongIdLength,
|
|
16
|
+
mockInvalidEventMissingPubkey,
|
|
17
|
+
mockInvalidEventWrongPubkeyLength,
|
|
18
|
+
mockInvalidEventMissingCreatedAt,
|
|
19
|
+
mockInvalidEventWrongCreatedAtType,
|
|
20
|
+
mockInvalidEventMissingKind,
|
|
21
|
+
mockInvalidEventKindOutOfRange,
|
|
22
|
+
mockInvalidEventMissingSig,
|
|
23
|
+
mockInvalidEventWrongSigLength,
|
|
24
|
+
mockInvalidEventTagsNotArray
|
|
25
|
+
} from '../mocks/event-mocks.js'
|
|
26
|
+
|
|
27
|
+
// Unit under test
|
|
28
|
+
import Event from '../../../src/entities/event.js'
|
|
29
|
+
|
|
30
|
+
describe('#event.js', () => {
|
|
31
|
+
describe('#isValid()', () => {
|
|
32
|
+
it('should return true for valid kind 0 event', () => {
|
|
33
|
+
const event = new Event(mockKind0Event)
|
|
34
|
+
assert.isTrue(event.isValid())
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should return true for valid kind 1 event', () => {
|
|
38
|
+
const event = new Event(mockKind1Event)
|
|
39
|
+
assert.isTrue(event.isValid())
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should return true for valid kind 3 event', () => {
|
|
43
|
+
const event = new Event(mockKind3Event)
|
|
44
|
+
assert.isTrue(event.isValid())
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should return true for valid kind 7 event', () => {
|
|
48
|
+
const event = new Event(mockKind7Event)
|
|
49
|
+
assert.isTrue(event.isValid())
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should return false for event missing id', () => {
|
|
53
|
+
const event = new Event(mockInvalidEventMissingId)
|
|
54
|
+
assert.isFalse(event.isValid())
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should return false for event with wrong id length', () => {
|
|
58
|
+
const event = new Event(mockInvalidEventWrongIdLength)
|
|
59
|
+
assert.isFalse(event.isValid())
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should return false for event missing pubkey', () => {
|
|
63
|
+
const event = new Event(mockInvalidEventMissingPubkey)
|
|
64
|
+
assert.isFalse(event.isValid())
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should return false for event with wrong pubkey length', () => {
|
|
68
|
+
const event = new Event(mockInvalidEventWrongPubkeyLength)
|
|
69
|
+
assert.isFalse(event.isValid())
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should return false for event missing created_at', () => {
|
|
73
|
+
const event = new Event(mockInvalidEventMissingCreatedAt)
|
|
74
|
+
assert.isFalse(event.isValid())
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should return false for event with wrong created_at type', () => {
|
|
78
|
+
const event = new Event(mockInvalidEventWrongCreatedAtType)
|
|
79
|
+
assert.isFalse(event.isValid())
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should return false for event missing kind', () => {
|
|
83
|
+
const event = new Event(mockInvalidEventMissingKind)
|
|
84
|
+
assert.isFalse(event.isValid())
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should return false for event with kind out of range', () => {
|
|
88
|
+
const event = new Event(mockInvalidEventKindOutOfRange)
|
|
89
|
+
assert.isFalse(event.isValid())
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should return false for event missing sig', () => {
|
|
93
|
+
const event = new Event(mockInvalidEventMissingSig)
|
|
94
|
+
assert.isFalse(event.isValid())
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should return false for event with wrong sig length', () => {
|
|
98
|
+
const event = new Event(mockInvalidEventWrongSigLength)
|
|
99
|
+
assert.isFalse(event.isValid())
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('should return false for event with tags not an array', () => {
|
|
103
|
+
const event = new Event(mockInvalidEventTagsNotArray)
|
|
104
|
+
assert.isFalse(event.isValid())
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('#toJSON()', () => {
|
|
109
|
+
it('should serialize event to JSON correctly', () => {
|
|
110
|
+
const event = new Event(mockKind1Event)
|
|
111
|
+
const json = event.toJSON()
|
|
112
|
+
|
|
113
|
+
assert.property(json, 'id')
|
|
114
|
+
assert.property(json, 'pubkey')
|
|
115
|
+
assert.property(json, 'created_at')
|
|
116
|
+
assert.property(json, 'kind')
|
|
117
|
+
assert.property(json, 'tags')
|
|
118
|
+
assert.property(json, 'content')
|
|
119
|
+
assert.property(json, 'sig')
|
|
120
|
+
|
|
121
|
+
assert.equal(json.id, mockKind1Event.id)
|
|
122
|
+
assert.equal(json.pubkey, mockKind1Event.pubkey)
|
|
123
|
+
assert.equal(json.created_at, mockKind1Event.created_at)
|
|
124
|
+
assert.equal(json.kind, mockKind1Event.kind)
|
|
125
|
+
assert.deepEqual(json.tags, mockKind1Event.tags)
|
|
126
|
+
assert.equal(json.content, mockKind1Event.content)
|
|
127
|
+
assert.equal(json.sig, mockKind1Event.sig)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should serialize event with tags correctly', () => {
|
|
131
|
+
const event = new Event(mockKind3Event)
|
|
132
|
+
const json = event.toJSON()
|
|
133
|
+
|
|
134
|
+
assert.isArray(json.tags)
|
|
135
|
+
assert.equal(json.tags.length, 1)
|
|
136
|
+
assert.deepEqual(json.tags, mockKind3Event.tags)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Mock Express request/response objects for controller unit tests.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Mock Express request object
|
|
6
|
+
export function createMockRequest (overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
body: {},
|
|
9
|
+
params: {},
|
|
10
|
+
query: {},
|
|
11
|
+
method: 'GET',
|
|
12
|
+
path: '/',
|
|
13
|
+
...overrides
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Mock Express response object
|
|
18
|
+
export function createMockResponse () {
|
|
19
|
+
const res = {
|
|
20
|
+
statusCode: 200,
|
|
21
|
+
jsonData: null,
|
|
22
|
+
statusValue: null,
|
|
23
|
+
headers: {},
|
|
24
|
+
writeData: [],
|
|
25
|
+
endCalled: false,
|
|
26
|
+
writable: true, // Stream is writable by default
|
|
27
|
+
destroyed: false, // Stream is not destroyed by default
|
|
28
|
+
closed: false, // Stream is not closed by default
|
|
29
|
+
eventHandlers: {} // Store event handlers
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
res.status = function (code) {
|
|
33
|
+
res.statusCode = code
|
|
34
|
+
res.statusValue = code
|
|
35
|
+
return res
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
res.json = function (data) {
|
|
39
|
+
res.jsonData = data
|
|
40
|
+
return res
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
res.setHeader = function (name, value) {
|
|
44
|
+
res.headers[name] = value
|
|
45
|
+
return res
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
res.write = function (data) {
|
|
49
|
+
res.writeData.push(data)
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
res.end = function () {
|
|
54
|
+
res.endCalled = true
|
|
55
|
+
return res
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
res.on = function (event, callback) {
|
|
59
|
+
// Store event handlers for different event types
|
|
60
|
+
if (!res.eventHandlers[event]) {
|
|
61
|
+
res.eventHandlers[event] = []
|
|
62
|
+
}
|
|
63
|
+
res.eventHandlers[event].push(callback)
|
|
64
|
+
|
|
65
|
+
// For backward compatibility with existing tests
|
|
66
|
+
if (event === 'close') {
|
|
67
|
+
res.closeCallback = callback
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return res
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Helper to trigger an event (useful for testing)
|
|
74
|
+
res.trigger = function (event, ...args) {
|
|
75
|
+
if (res.eventHandlers[event]) {
|
|
76
|
+
for (const handler of res.eventHandlers[event]) {
|
|
77
|
+
handler(...args)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return res
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Helper to create a mock request with body
|
|
86
|
+
export function createMockRequestWithBody (body) {
|
|
87
|
+
return createMockRequest({ body })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Helper to create a mock request with params
|
|
91
|
+
export function createMockRequestWithParams (params) {
|
|
92
|
+
return createMockRequest({ params })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Helper to create a mock request with query
|
|
96
|
+
export function createMockRequestWithQuery (query) {
|
|
97
|
+
return createMockRequest({ query })
|
|
98
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Mock event data for unit tests.
|
|
3
|
+
Contains mock Nostr events for various event kinds.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Alice's public key from examples
|
|
7
|
+
const alicePubKey = '2c7e76c0f8dc1dca9d0197c7d19be580a8d074ccada6a2f6ebe056ae41092e92'
|
|
8
|
+
const bobPubKey = 'b'.repeat(64)
|
|
9
|
+
|
|
10
|
+
// Valid event ID (64 hex chars)
|
|
11
|
+
const validEventId = 'd09b4c5da59be3cd2768aa53fa78b77bf4859084c94f3bf26d401f004a9c8167'
|
|
12
|
+
// Valid signature (128 hex chars)
|
|
13
|
+
const validSig = 'a'.repeat(128)
|
|
14
|
+
|
|
15
|
+
// Kind 0: Profile metadata event
|
|
16
|
+
const mockKind0Event = {
|
|
17
|
+
id: validEventId,
|
|
18
|
+
pubkey: alicePubKey,
|
|
19
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
20
|
+
kind: 0,
|
|
21
|
+
tags: [],
|
|
22
|
+
content: JSON.stringify({
|
|
23
|
+
name: 'Alice',
|
|
24
|
+
about: 'Hello, I am Alice!',
|
|
25
|
+
picture: 'https://example.com/alice.jpg'
|
|
26
|
+
}),
|
|
27
|
+
sig: validSig
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Kind 1: Text post event
|
|
31
|
+
const mockKind1Event = {
|
|
32
|
+
id: validEventId,
|
|
33
|
+
pubkey: alicePubKey,
|
|
34
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
35
|
+
kind: 1,
|
|
36
|
+
tags: [],
|
|
37
|
+
content: 'This is a test message',
|
|
38
|
+
sig: validSig
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Kind 3: Follow list event
|
|
42
|
+
const mockKind3Event = {
|
|
43
|
+
id: validEventId,
|
|
44
|
+
pubkey: alicePubKey,
|
|
45
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
46
|
+
kind: 3,
|
|
47
|
+
tags: [
|
|
48
|
+
['p', bobPubKey, 'wss://nostr-relay.psfoundation.info', 'bob']
|
|
49
|
+
],
|
|
50
|
+
content: '',
|
|
51
|
+
sig: validSig
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Kind 7: Reaction/like event
|
|
55
|
+
const mockKind7Event = {
|
|
56
|
+
id: validEventId,
|
|
57
|
+
pubkey: bobPubKey,
|
|
58
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
59
|
+
kind: 7,
|
|
60
|
+
tags: [
|
|
61
|
+
['e', validEventId, 'wss://nostr-relay.psfoundation.info'],
|
|
62
|
+
['p', alicePubKey, 'wss://nostr-relay.psfoundation.info']
|
|
63
|
+
],
|
|
64
|
+
content: '+',
|
|
65
|
+
sig: validSig
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Invalid events for testing validation
|
|
69
|
+
const mockInvalidEventMissingId = {
|
|
70
|
+
pubkey: alicePubKey,
|
|
71
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
72
|
+
kind: 1,
|
|
73
|
+
tags: [],
|
|
74
|
+
content: 'Test',
|
|
75
|
+
sig: validSig
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const mockInvalidEventWrongIdLength = {
|
|
79
|
+
id: 'short',
|
|
80
|
+
pubkey: alicePubKey,
|
|
81
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
82
|
+
kind: 1,
|
|
83
|
+
tags: [],
|
|
84
|
+
content: 'Test',
|
|
85
|
+
sig: validSig
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const mockInvalidEventMissingPubkey = {
|
|
89
|
+
id: validEventId,
|
|
90
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
91
|
+
kind: 1,
|
|
92
|
+
tags: [],
|
|
93
|
+
content: 'Test',
|
|
94
|
+
sig: validSig
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const mockInvalidEventWrongPubkeyLength = {
|
|
98
|
+
id: validEventId,
|
|
99
|
+
pubkey: 'short',
|
|
100
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
101
|
+
kind: 1,
|
|
102
|
+
tags: [],
|
|
103
|
+
content: 'Test',
|
|
104
|
+
sig: validSig
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const mockInvalidEventMissingCreatedAt = {
|
|
108
|
+
id: validEventId,
|
|
109
|
+
pubkey: alicePubKey,
|
|
110
|
+
kind: 1,
|
|
111
|
+
tags: [],
|
|
112
|
+
content: 'Test',
|
|
113
|
+
sig: validSig
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const mockInvalidEventWrongCreatedAtType = {
|
|
117
|
+
id: validEventId,
|
|
118
|
+
pubkey: alicePubKey,
|
|
119
|
+
created_at: 'not-a-number',
|
|
120
|
+
kind: 1,
|
|
121
|
+
tags: [],
|
|
122
|
+
content: 'Test',
|
|
123
|
+
sig: validSig
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const mockInvalidEventMissingKind = {
|
|
127
|
+
id: validEventId,
|
|
128
|
+
pubkey: alicePubKey,
|
|
129
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
130
|
+
tags: [],
|
|
131
|
+
content: 'Test',
|
|
132
|
+
sig: validSig
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const mockInvalidEventKindOutOfRange = {
|
|
136
|
+
id: validEventId,
|
|
137
|
+
pubkey: alicePubKey,
|
|
138
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
139
|
+
kind: 70000, // Out of range (0-65535)
|
|
140
|
+
tags: [],
|
|
141
|
+
content: 'Test',
|
|
142
|
+
sig: validSig
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const mockInvalidEventMissingSig = {
|
|
146
|
+
id: validEventId,
|
|
147
|
+
pubkey: alicePubKey,
|
|
148
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
149
|
+
kind: 1,
|
|
150
|
+
tags: [],
|
|
151
|
+
content: 'Test'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const mockInvalidEventWrongSigLength = {
|
|
155
|
+
id: validEventId,
|
|
156
|
+
pubkey: alicePubKey,
|
|
157
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
158
|
+
kind: 1,
|
|
159
|
+
tags: [],
|
|
160
|
+
content: 'Test',
|
|
161
|
+
sig: 'short'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const mockInvalidEventTagsNotArray = {
|
|
165
|
+
id: validEventId,
|
|
166
|
+
pubkey: alicePubKey,
|
|
167
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
168
|
+
kind: 1,
|
|
169
|
+
tags: 'not-an-array',
|
|
170
|
+
content: 'Test',
|
|
171
|
+
sig: validSig
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export {
|
|
175
|
+
mockKind0Event,
|
|
176
|
+
mockKind1Event,
|
|
177
|
+
mockKind3Event,
|
|
178
|
+
mockKind7Event,
|
|
179
|
+
mockInvalidEventMissingId,
|
|
180
|
+
mockInvalidEventWrongIdLength,
|
|
181
|
+
mockInvalidEventMissingPubkey,
|
|
182
|
+
mockInvalidEventWrongPubkeyLength,
|
|
183
|
+
mockInvalidEventMissingCreatedAt,
|
|
184
|
+
mockInvalidEventWrongCreatedAtType,
|
|
185
|
+
mockInvalidEventMissingKind,
|
|
186
|
+
mockInvalidEventKindOutOfRange,
|
|
187
|
+
mockInvalidEventMissingSig,
|
|
188
|
+
mockInvalidEventWrongSigLength,
|
|
189
|
+
mockInvalidEventTagsNotArray,
|
|
190
|
+
alicePubKey,
|
|
191
|
+
bobPubKey,
|
|
192
|
+
validEventId,
|
|
193
|
+
validSig
|
|
194
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Unit tests for BlockchainUseCases.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { assert } from 'chai'
|
|
6
|
+
import sinon from 'sinon'
|
|
7
|
+
|
|
8
|
+
import BlockchainUseCases from '../../../src/use-cases/full-node-blockchain-use-cases.js'
|
|
9
|
+
|
|
10
|
+
describe('#full-node-blockchain-use-cases.js', () => {
|
|
11
|
+
let sandbox
|
|
12
|
+
let mockAdapters
|
|
13
|
+
let uut
|
|
14
|
+
|
|
15
|
+
const createAdapters = () => {
|
|
16
|
+
return {
|
|
17
|
+
fullNode: {
|
|
18
|
+
call: sandbox.stub()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
sandbox = sinon.createSandbox()
|
|
25
|
+
mockAdapters = createAdapters()
|
|
26
|
+
uut = new BlockchainUseCases({ adapters: mockAdapters })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
sandbox.restore()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('#constructor()', () => {
|
|
34
|
+
it('should require adapters', () => {
|
|
35
|
+
assert.throws(() => {
|
|
36
|
+
// eslint-disable-next-line no-new
|
|
37
|
+
new BlockchainUseCases()
|
|
38
|
+
}, /Adapters instance required/)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should require full node adapter', () => {
|
|
42
|
+
assert.throws(() => {
|
|
43
|
+
// eslint-disable-next-line no-new
|
|
44
|
+
new BlockchainUseCases({ adapters: {} })
|
|
45
|
+
}, /Full node adapter required/)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('#getBestBlockHash()', () => {
|
|
50
|
+
it('should call full node adapter without parameters', async () => {
|
|
51
|
+
mockAdapters.fullNode.call.resolves('hash')
|
|
52
|
+
|
|
53
|
+
const result = await uut.getBestBlockHash()
|
|
54
|
+
|
|
55
|
+
assert.equal(result, 'hash')
|
|
56
|
+
assert.isTrue(mockAdapters.fullNode.call.calledOnceWithExactly('getbestblockhash'))
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('#getBlockHeaders()', () => {
|
|
61
|
+
it('should call adapter for each hash and return aggregated result', async () => {
|
|
62
|
+
const hashes = ['a'.repeat(64), 'b'.repeat(64)]
|
|
63
|
+
mockAdapters.fullNode.call
|
|
64
|
+
.onFirstCall().resolves('header-1')
|
|
65
|
+
.onSecondCall().resolves('header-2')
|
|
66
|
+
|
|
67
|
+
const result = await uut.getBlockHeaders({ hashes, verbose: true })
|
|
68
|
+
|
|
69
|
+
assert.deepEqual(result, ['header-1', 'header-2'])
|
|
70
|
+
assert.isTrue(
|
|
71
|
+
mockAdapters.fullNode.call.calledWithExactly(
|
|
72
|
+
'getblockheader',
|
|
73
|
+
[hashes[0], true],
|
|
74
|
+
`getblockheader-${hashes[0]}`
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
assert.isTrue(
|
|
78
|
+
mockAdapters.fullNode.call.calledWithExactly(
|
|
79
|
+
'getblockheader',
|
|
80
|
+
[hashes[1], true],
|
|
81
|
+
`getblockheader-${hashes[1]}`
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should rethrow errors from adapter', async () => {
|
|
87
|
+
const hashes = ['a'.repeat(64)]
|
|
88
|
+
mockAdapters.fullNode.call.rejects(new Error('failure'))
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await uut.getBlockHeaders({ hashes })
|
|
92
|
+
assert.fail('Unexpected success')
|
|
93
|
+
} catch (err) {
|
|
94
|
+
assert.equal(err.message, 'failure')
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe('#getTxOut()', () => {
|
|
100
|
+
it('should pass parameters to full node call', async () => {
|
|
101
|
+
mockAdapters.fullNode.call.resolves({ value: 1 })
|
|
102
|
+
|
|
103
|
+
const result = await uut.getTxOut({
|
|
104
|
+
txid: 'txid',
|
|
105
|
+
n: 0,
|
|
106
|
+
includeMempool: true
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
assert.deepEqual(result, { value: 1 })
|
|
110
|
+
assert.isTrue(
|
|
111
|
+
mockAdapters.fullNode.call.calledOnceWithExactly(
|
|
112
|
+
'gettxout',
|
|
113
|
+
['txid', 0, true]
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('#verifyTxOutProofs()', () => {
|
|
120
|
+
it('should call adapter for each proof and return aggregated results', async () => {
|
|
121
|
+
const proofs = ['proof-1', 'proof-2']
|
|
122
|
+
mockAdapters.fullNode.call.onFirstCall().resolves(['txid-1'])
|
|
123
|
+
mockAdapters.fullNode.call.onSecondCall().resolves(['txid-2'])
|
|
124
|
+
|
|
125
|
+
const result = await uut.verifyTxOutProofs({ proofs })
|
|
126
|
+
|
|
127
|
+
assert.deepEqual(result, [['txid-1'], ['txid-2']])
|
|
128
|
+
assert.isTrue(
|
|
129
|
+
mockAdapters.fullNode.call.calledWithExactly(
|
|
130
|
+
'verifytxoutproof',
|
|
131
|
+
['proof-1'],
|
|
132
|
+
`verifytxoutproof-${proofs[0].slice(0, 16)}`
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
})
|