@snaha/swarm-id 0.0.1
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 +431 -0
- package/dist/chunk/bmt.d.ts +17 -0
- package/dist/chunk/bmt.d.ts.map +1 -0
- package/dist/chunk/cac.d.ts +18 -0
- package/dist/chunk/cac.d.ts.map +1 -0
- package/dist/chunk/constants.d.ts +10 -0
- package/dist/chunk/constants.d.ts.map +1 -0
- package/dist/chunk/encrypted-cac.d.ts +48 -0
- package/dist/chunk/encrypted-cac.d.ts.map +1 -0
- package/dist/chunk/encryption.d.ts +86 -0
- package/dist/chunk/encryption.d.ts.map +1 -0
- package/dist/chunk/index.d.ts +6 -0
- package/dist/chunk/index.d.ts.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/proxy/act/act.d.ts +78 -0
- package/dist/proxy/act/act.d.ts.map +1 -0
- package/dist/proxy/act/crypto.d.ts +44 -0
- package/dist/proxy/act/crypto.d.ts.map +1 -0
- package/dist/proxy/act/grantee-list.d.ts +82 -0
- package/dist/proxy/act/grantee-list.d.ts.map +1 -0
- package/dist/proxy/act/history.d.ts +183 -0
- package/dist/proxy/act/history.d.ts.map +1 -0
- package/dist/proxy/act/index.d.ts +104 -0
- package/dist/proxy/act/index.d.ts.map +1 -0
- package/dist/proxy/chunking-encrypted.d.ts +14 -0
- package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
- package/dist/proxy/chunking.d.ts +15 -0
- package/dist/proxy/chunking.d.ts.map +1 -0
- package/dist/proxy/download-data.d.ts +16 -0
- package/dist/proxy/download-data.d.ts.map +1 -0
- package/dist/proxy/feed-manifest.d.ts +62 -0
- package/dist/proxy/feed-manifest.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
- package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/index.d.ts +35 -0
- package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/types.d.ts +109 -0
- package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
- package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
- package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
- package/dist/proxy/feeds/index.d.ts +5 -0
- package/dist/proxy/feeds/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
- package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/index.d.ts +23 -0
- package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/types.d.ts +80 -0
- package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
- package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
- package/dist/proxy/index.d.ts +6 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/manifest-builder.d.ts +183 -0
- package/dist/proxy/manifest-builder.d.ts.map +1 -0
- package/dist/proxy/mantaray-encrypted.d.ts +27 -0
- package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
- package/dist/proxy/mantaray.d.ts +26 -0
- package/dist/proxy/mantaray.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +29 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/upload-data.d.ts +17 -0
- package/dist/proxy/upload-data.d.ts.map +1 -0
- package/dist/proxy/upload-encrypted-data.d.ts +103 -0
- package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
- package/dist/schemas.d.ts +240 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/storage/debounced-uploader.d.ts +62 -0
- package/dist/storage/debounced-uploader.d.ts.map +1 -0
- package/dist/storage/utilization-store.d.ts +108 -0
- package/dist/storage/utilization-store.d.ts.map +1 -0
- package/dist/swarm-id-auth.d.ts +74 -0
- package/dist/swarm-id-auth.d.ts.map +1 -0
- package/dist/swarm-id-auth.js +2 -0
- package/dist/swarm-id-auth.js.map +1 -0
- package/dist/swarm-id-client.d.ts +878 -0
- package/dist/swarm-id-client.d.ts.map +1 -0
- package/dist/swarm-id-client.js +2 -0
- package/dist/swarm-id-client.js.map +1 -0
- package/dist/swarm-id-proxy.d.ts +236 -0
- package/dist/swarm-id-proxy.d.ts.map +1 -0
- package/dist/swarm-id-proxy.js +2 -0
- package/dist/swarm-id-proxy.js.map +1 -0
- package/dist/swarm-id.esm.js +2 -0
- package/dist/swarm-id.esm.js.map +1 -0
- package/dist/swarm-id.umd.js +2 -0
- package/dist/swarm-id.umd.js.map +1 -0
- package/dist/sync/index.d.ts +9 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/key-derivation.d.ts +25 -0
- package/dist/sync/key-derivation.d.ts.map +1 -0
- package/dist/sync/restore-account.d.ts +28 -0
- package/dist/sync/restore-account.d.ts.map +1 -0
- package/dist/sync/serialization.d.ts +16 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/store-interfaces.d.ts +53 -0
- package/dist/sync/store-interfaces.d.ts.map +1 -0
- package/dist/sync/sync-account.d.ts +44 -0
- package/dist/sync/sync-account.d.ts.map +1 -0
- package/dist/sync/types.d.ts +13 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/test-fixtures.d.ts +17 -0
- package/dist/test-fixtures.d.ts.map +1 -0
- package/dist/types-BD_VkNn0.js +2 -0
- package/dist/types-BD_VkNn0.js.map +1 -0
- package/dist/types-lJCaT-50.js +2 -0
- package/dist/types-lJCaT-50.js.map +1 -0
- package/dist/types.d.ts +2157 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/account-payload.d.ts +94 -0
- package/dist/utils/account-payload.d.ts.map +1 -0
- package/dist/utils/account-state-snapshot.d.ts +38 -0
- package/dist/utils/account-state-snapshot.d.ts.map +1 -0
- package/dist/utils/backup-encryption.d.ts +127 -0
- package/dist/utils/backup-encryption.d.ts.map +1 -0
- package/dist/utils/batch-utilization.d.ts +432 -0
- package/dist/utils/batch-utilization.d.ts.map +1 -0
- package/dist/utils/constants.d.ts +11 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/hex.d.ts +17 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/key-derivation.d.ts +92 -0
- package/dist/utils/key-derivation.d.ts.map +1 -0
- package/dist/utils/storage-managers.d.ts +65 -0
- package/dist/utils/storage-managers.d.ts.map +1 -0
- package/dist/utils/swarm-id-export.d.ts +24 -0
- package/dist/utils/swarm-id-export.d.ts.map +1 -0
- package/dist/utils/ttl.d.ts +49 -0
- package/dist/utils/ttl.d.ts.map +1 -0
- package/dist/utils/url.d.ts +41 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/versioned-storage.d.ts +131 -0
- package/dist/utils/versioned-storage.d.ts.map +1 -0
- package/package.json +78 -0
- package/src/chunk/bmt.test.ts +217 -0
- package/src/chunk/bmt.ts +57 -0
- package/src/chunk/cac.test.ts +214 -0
- package/src/chunk/cac.ts +65 -0
- package/src/chunk/constants.ts +18 -0
- package/src/chunk/encrypted-cac.test.ts +385 -0
- package/src/chunk/encrypted-cac.ts +131 -0
- package/src/chunk/encryption.test.ts +352 -0
- package/src/chunk/encryption.ts +300 -0
- package/src/chunk/index.ts +47 -0
- package/src/index.ts +430 -0
- package/src/proxy/act/act.test.ts +278 -0
- package/src/proxy/act/act.ts +158 -0
- package/src/proxy/act/bee-compat.test.ts +948 -0
- package/src/proxy/act/crypto.test.ts +436 -0
- package/src/proxy/act/crypto.ts +376 -0
- package/src/proxy/act/grantee-list.test.ts +393 -0
- package/src/proxy/act/grantee-list.ts +239 -0
- package/src/proxy/act/history.test.ts +360 -0
- package/src/proxy/act/history.ts +413 -0
- package/src/proxy/act/index.test.ts +748 -0
- package/src/proxy/act/index.ts +853 -0
- package/src/proxy/chunking-encrypted.ts +95 -0
- package/src/proxy/chunking.ts +65 -0
- package/src/proxy/download-data.ts +448 -0
- package/src/proxy/feed-manifest.ts +174 -0
- package/src/proxy/feeds/epochs/async-finder.ts +372 -0
- package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
- package/src/proxy/feeds/epochs/epoch.ts +181 -0
- package/src/proxy/feeds/epochs/finder.ts +282 -0
- package/src/proxy/feeds/epochs/index.ts +73 -0
- package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
- package/src/proxy/feeds/epochs/test-utils.ts +274 -0
- package/src/proxy/feeds/epochs/types.ts +128 -0
- package/src/proxy/feeds/epochs/updater.ts +192 -0
- package/src/proxy/feeds/epochs/utils.ts +62 -0
- package/src/proxy/feeds/index.ts +5 -0
- package/src/proxy/feeds/sequence/async-finder.ts +31 -0
- package/src/proxy/feeds/sequence/finder.ts +73 -0
- package/src/proxy/feeds/sequence/index.ts +54 -0
- package/src/proxy/feeds/sequence/integration.test.ts +966 -0
- package/src/proxy/feeds/sequence/types.ts +103 -0
- package/src/proxy/feeds/sequence/updater.ts +71 -0
- package/src/proxy/index.ts +5 -0
- package/src/proxy/manifest-builder.test.ts +427 -0
- package/src/proxy/manifest-builder.ts +679 -0
- package/src/proxy/mantaray-encrypted.ts +78 -0
- package/src/proxy/mantaray.ts +104 -0
- package/src/proxy/types.ts +32 -0
- package/src/proxy/upload-data.ts +189 -0
- package/src/proxy/upload-encrypted-data.ts +658 -0
- package/src/schemas.ts +299 -0
- package/src/storage/debounced-uploader.ts +192 -0
- package/src/storage/utilization-store.ts +397 -0
- package/src/swarm-id-client.test.ts +99 -0
- package/src/swarm-id-client.ts +3095 -0
- package/src/swarm-id-proxy.ts +3891 -0
- package/src/sync/index.ts +28 -0
- package/src/sync/restore-account.ts +90 -0
- package/src/sync/serialization.ts +39 -0
- package/src/sync/store-interfaces.ts +62 -0
- package/src/sync/sync-account.test.ts +302 -0
- package/src/sync/sync-account.ts +396 -0
- package/src/sync/types.ts +11 -0
- package/src/test-fixtures.ts +109 -0
- package/src/types.ts +1651 -0
- package/src/utils/account-state-snapshot.test.ts +595 -0
- package/src/utils/account-state-snapshot.ts +94 -0
- package/src/utils/backup-encryption.test.ts +442 -0
- package/src/utils/backup-encryption.ts +352 -0
- package/src/utils/batch-utilization.ts +1309 -0
- package/src/utils/constants.ts +20 -0
- package/src/utils/hex.ts +27 -0
- package/src/utils/key-derivation.ts +197 -0
- package/src/utils/storage-managers.ts +365 -0
- package/src/utils/ttl.ts +129 -0
- package/src/utils/url.test.ts +136 -0
- package/src/utils/url.ts +71 -0
- package/src/utils/versioned-storage.ts +323 -0
|
@@ -0,0 +1,966 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for sequential feeds
|
|
3
|
+
*
|
|
4
|
+
* Based on the Go implementation tests from bee/pkg/feeds/sequence
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"
|
|
8
|
+
import { Binary } from "cafe-utility"
|
|
9
|
+
import type { Bee } from "@ethersphere/bee-js"
|
|
10
|
+
import { PrivateKey } from "@ethersphere/bee-js"
|
|
11
|
+
import { SyncSequentialFinder } from "./finder"
|
|
12
|
+
import { AsyncSequentialFinder } from "./async-finder"
|
|
13
|
+
import { BasicSequentialUpdater } from "./updater"
|
|
14
|
+
import {
|
|
15
|
+
MockBee,
|
|
16
|
+
MockChunkStore,
|
|
17
|
+
createTestSigner,
|
|
18
|
+
createTestTopic,
|
|
19
|
+
createTestReference,
|
|
20
|
+
createMockStamper,
|
|
21
|
+
mockFetch,
|
|
22
|
+
} from "../epochs/test-utils"
|
|
23
|
+
|
|
24
|
+
const SPAN_SIZE = 8
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create 64-byte reference (encrypted reference)
|
|
28
|
+
*/
|
|
29
|
+
function createTestReference64(seed: number): Uint8Array {
|
|
30
|
+
const ref = new Uint8Array(64)
|
|
31
|
+
const view = new DataView(ref.buffer)
|
|
32
|
+
view.setBigUint64(0, BigInt(seed), false)
|
|
33
|
+
for (let i = 32; i < 64; i++) {
|
|
34
|
+
ref[i] = (seed + i) & 0xff
|
|
35
|
+
}
|
|
36
|
+
return ref
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* MockBee that counts download calls
|
|
41
|
+
*/
|
|
42
|
+
class CountingMockBee extends MockBee {
|
|
43
|
+
public downloadCalls = 0
|
|
44
|
+
public uploadCalls = 0
|
|
45
|
+
|
|
46
|
+
override async downloadChunk(reference: string): Promise<Uint8Array> {
|
|
47
|
+
this.downloadCalls++
|
|
48
|
+
const lower = reference.toLowerCase()
|
|
49
|
+
if (!this.getStore().has(lower)) {
|
|
50
|
+
throw new Error("Request failed with status code 404")
|
|
51
|
+
}
|
|
52
|
+
return this.getStore().get(lower)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override async uploadChunk(
|
|
56
|
+
data: Uint8Array,
|
|
57
|
+
postageBatchId: string,
|
|
58
|
+
): Promise<{ reference: string }> {
|
|
59
|
+
this.uploadCalls++
|
|
60
|
+
return super.uploadChunk(data, postageBatchId)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* MockBee that simulates network failures
|
|
66
|
+
*/
|
|
67
|
+
class FailingMockBee extends MockBee {
|
|
68
|
+
private failureCount = 0
|
|
69
|
+
private maxFailures: number
|
|
70
|
+
|
|
71
|
+
constructor(store?: MockChunkStore, maxFailures = 3) {
|
|
72
|
+
super(store)
|
|
73
|
+
this.maxFailures = maxFailures
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override async downloadChunk(reference: string): Promise<Uint8Array> {
|
|
77
|
+
if (this.failureCount < this.maxFailures) {
|
|
78
|
+
this.failureCount++
|
|
79
|
+
throw new Error("Request failed with status code 500")
|
|
80
|
+
}
|
|
81
|
+
return super.downloadChunk(reference)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
resetFailures(): void {
|
|
85
|
+
this.failureCount = 0
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* MockBee that simulates mixed error types
|
|
91
|
+
*/
|
|
92
|
+
class MixedErrorMockBee extends CountingMockBee {
|
|
93
|
+
override async downloadChunk(reference: string): Promise<Uint8Array> {
|
|
94
|
+
this.downloadCalls++
|
|
95
|
+
const lower = reference.toLowerCase()
|
|
96
|
+
if (this.getStore().has(lower)) {
|
|
97
|
+
return this.getStore().get(lower)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const selector = lower.charCodeAt(0) % 3
|
|
101
|
+
if (selector === 0) {
|
|
102
|
+
throw new Error("Request failed with status code 404")
|
|
103
|
+
}
|
|
104
|
+
if (selector === 1) {
|
|
105
|
+
throw new Error("Request failed with status code 500")
|
|
106
|
+
}
|
|
107
|
+
throw new Error("timeout of 2000ms exceeded")
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Mock uploadSOC to store SOC data in the mock store.
|
|
113
|
+
*/
|
|
114
|
+
vi.mock("../../upload-encrypted-data", async (importOriginal) => {
|
|
115
|
+
const mod =
|
|
116
|
+
await importOriginal<typeof import("../../upload-encrypted-data")>()
|
|
117
|
+
const { Binary } = await import("cafe-utility")
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
...mod,
|
|
121
|
+
uploadSOC: async (
|
|
122
|
+
bee: any,
|
|
123
|
+
_stamper: any,
|
|
124
|
+
signer: any,
|
|
125
|
+
identifier: any,
|
|
126
|
+
data: Uint8Array,
|
|
127
|
+
) => {
|
|
128
|
+
// Validate data size (1-4096 bytes) - matching real uploadSOC
|
|
129
|
+
if (data.length < 1 || data.length > 4096) {
|
|
130
|
+
throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Create span (little-endian uint64 of data length)
|
|
134
|
+
const span = new Uint8Array(SPAN_SIZE)
|
|
135
|
+
const spanView = new DataView(span.buffer)
|
|
136
|
+
spanView.setBigUint64(0, BigInt(data.length), true)
|
|
137
|
+
|
|
138
|
+
// Sign: hash(identifier + hash(span + data))
|
|
139
|
+
const contentHash = Binary.keccak256(Binary.concatBytes(span, data))
|
|
140
|
+
const toSign = Binary.concatBytes(identifier.toUint8Array(), contentHash)
|
|
141
|
+
const signature = signer.sign(toSign)
|
|
142
|
+
|
|
143
|
+
// Build SOC data: identifier(32) + signature(65) + span(8) + payload
|
|
144
|
+
const socData = Binary.concatBytes(
|
|
145
|
+
identifier.toUint8Array(),
|
|
146
|
+
signature.toUint8Array(),
|
|
147
|
+
span,
|
|
148
|
+
data,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
// Calculate SOC address: Keccak256(identifier + owner)
|
|
152
|
+
const owner = signer.publicKey().address()
|
|
153
|
+
const socAddress = Binary.keccak256(
|
|
154
|
+
Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
// Store directly in mock store (bypassing fetch)
|
|
158
|
+
const store = bee.getStore()
|
|
159
|
+
const reference = Binary.uint8ArrayToHex(socAddress)
|
|
160
|
+
await store.put(reference, socData)
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
socAddress,
|
|
164
|
+
tagUid: 0,
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
uploadEncryptedSOC: async (
|
|
168
|
+
bee: any,
|
|
169
|
+
_stamper: any,
|
|
170
|
+
signer: any,
|
|
171
|
+
identifier: any,
|
|
172
|
+
data: Uint8Array,
|
|
173
|
+
) => {
|
|
174
|
+
// Validate data size (1-4096 bytes) - matching real uploadEncryptedSOC
|
|
175
|
+
if (data.length < 1 || data.length > 4096) {
|
|
176
|
+
throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Create span (little-endian uint64 of data length)
|
|
180
|
+
const span = new Uint8Array(SPAN_SIZE)
|
|
181
|
+
const spanView = new DataView(span.buffer)
|
|
182
|
+
spanView.setBigUint64(0, BigInt(data.length), true)
|
|
183
|
+
|
|
184
|
+
// Sign: hash(identifier + hash(span + data))
|
|
185
|
+
const contentHash = Binary.keccak256(Binary.concatBytes(span, data))
|
|
186
|
+
const toSign = Binary.concatBytes(identifier.toUint8Array(), contentHash)
|
|
187
|
+
const signature = signer.sign(toSign)
|
|
188
|
+
|
|
189
|
+
// Build SOC data: identifier(32) + signature(65) + span(8) + payload
|
|
190
|
+
const socData = Binary.concatBytes(
|
|
191
|
+
identifier.toUint8Array(),
|
|
192
|
+
signature.toUint8Array(),
|
|
193
|
+
span,
|
|
194
|
+
data,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
// Calculate SOC address: Keccak256(identifier + owner)
|
|
198
|
+
const owner = signer.publicKey().address()
|
|
199
|
+
const socAddress = Binary.keccak256(
|
|
200
|
+
Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
// Store directly in mock store (bypassing fetch/encryption)
|
|
204
|
+
const store = bee.getStore()
|
|
205
|
+
const reference = Binary.uint8ArrayToHex(socAddress)
|
|
206
|
+
await store.put(reference, socData)
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
socAddress,
|
|
210
|
+
encryptionKey: new Uint8Array(32),
|
|
211
|
+
tagUid: 0,
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe("Sequential Feeds Integration", () => {
|
|
218
|
+
let store: MockChunkStore
|
|
219
|
+
let bee: MockBee
|
|
220
|
+
let signer: ReturnType<typeof createTestSigner>
|
|
221
|
+
let topic: ReturnType<typeof createTestTopic>
|
|
222
|
+
let stamper: ReturnType<typeof createMockStamper>
|
|
223
|
+
|
|
224
|
+
beforeEach(() => {
|
|
225
|
+
store = new MockChunkStore()
|
|
226
|
+
bee = new MockBee(store)
|
|
227
|
+
signer = createTestSigner()
|
|
228
|
+
topic = createTestTopic()
|
|
229
|
+
stamper = createMockStamper()
|
|
230
|
+
mockFetch(store, signer.publicKey().address())
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
afterEach(() => {
|
|
234
|
+
store.clear()
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
describe("Basic Operations", () => {
|
|
238
|
+
it("should return undefined when no updates exist", async () => {
|
|
239
|
+
const owner = signer.publicKey().address()
|
|
240
|
+
const beeClient = bee as unknown as Bee
|
|
241
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
242
|
+
|
|
243
|
+
const result = await finder.findAt(0n, 0n)
|
|
244
|
+
expect(result.current).toBeUndefined()
|
|
245
|
+
expect(result.next).toBe(0n)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it("should store and retrieve latest update index", async () => {
|
|
249
|
+
const beeClient = bee as unknown as Bee
|
|
250
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
251
|
+
const owner = updater.getOwner()
|
|
252
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
253
|
+
|
|
254
|
+
const updates = 5
|
|
255
|
+
for (let i = 0; i < updates; i++) {
|
|
256
|
+
await updater.update(createTestReference(i), stamper)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const result = await finder.findAt(0n, 0n)
|
|
260
|
+
expect(result.current).toBe(BigInt(updates - 1))
|
|
261
|
+
expect(result.next).toBe(BigInt(updates))
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it("async finder should match sync finder", async () => {
|
|
265
|
+
const beeClient = bee as unknown as Bee
|
|
266
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
267
|
+
const owner = updater.getOwner()
|
|
268
|
+
const syncFinder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
269
|
+
const asyncFinder = new AsyncSequentialFinder(beeClient, topic, owner)
|
|
270
|
+
|
|
271
|
+
const updates = 3
|
|
272
|
+
for (let i = 0; i < updates; i++) {
|
|
273
|
+
await updater.update(createTestReference(i), stamper)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const syncResult = await syncFinder.findAt(0n, 0n)
|
|
277
|
+
const asyncResult = await asyncFinder.findAt(0n, 0n)
|
|
278
|
+
|
|
279
|
+
expect(asyncResult.current).toBe(syncResult.current)
|
|
280
|
+
expect(asyncResult.next).toBe(syncResult.next)
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
describe("Multiple Updates", () => {
|
|
285
|
+
it("should write and read 10 sequential updates", async () => {
|
|
286
|
+
const beeClient = bee as unknown as Bee
|
|
287
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
288
|
+
const owner = updater.getOwner()
|
|
289
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
290
|
+
|
|
291
|
+
// Write indices 0-9
|
|
292
|
+
for (let i = 0; i < 10; i++) {
|
|
293
|
+
await updater.update(createTestReference(i), stamper)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Verify finder returns correct result
|
|
297
|
+
const result = await finder.findAt(0n, 0n)
|
|
298
|
+
expect(result.current).toBe(9n)
|
|
299
|
+
expect(result.next).toBe(10n)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it(
|
|
303
|
+
"should write and read 50 sequential updates",
|
|
304
|
+
{ timeout: 10000 },
|
|
305
|
+
async () => {
|
|
306
|
+
const beeClient = bee as unknown as Bee
|
|
307
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
308
|
+
const owner = updater.getOwner()
|
|
309
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
310
|
+
|
|
311
|
+
// Larger scale test
|
|
312
|
+
const count = 50
|
|
313
|
+
for (let i = 0; i < count; i++) {
|
|
314
|
+
await updater.update(createTestReference(i), stamper)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const result = await finder.findAt(0n, 0n)
|
|
318
|
+
expect(result.current).toBe(BigInt(count - 1))
|
|
319
|
+
expect(result.next).toBe(BigInt(count))
|
|
320
|
+
},
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
it("should maintain correct ordering across updates", async () => {
|
|
324
|
+
const beeClient = bee as unknown as Bee
|
|
325
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
326
|
+
const finder = new SyncSequentialFinder(
|
|
327
|
+
beeClient,
|
|
328
|
+
topic,
|
|
329
|
+
updater.getOwner(),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
const references: Uint8Array[] = []
|
|
333
|
+
for (let i = 0; i < 5; i++) {
|
|
334
|
+
const ref = createTestReference(i * 100)
|
|
335
|
+
references.push(ref)
|
|
336
|
+
await updater.update(ref, stamper)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Verify each update is stored at its expected index
|
|
340
|
+
const result = await finder.findAt(0n, 0n)
|
|
341
|
+
expect(result.current).toBe(4n)
|
|
342
|
+
expect(result.next).toBe(5n)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it("should handle payload content correctly for each index", async () => {
|
|
346
|
+
const beeClient = bee as unknown as Bee
|
|
347
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
348
|
+
const owner = updater.getOwner()
|
|
349
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
350
|
+
|
|
351
|
+
// Write different payloads at different indices
|
|
352
|
+
const payloads = [
|
|
353
|
+
createTestReference(111),
|
|
354
|
+
createTestReference(222),
|
|
355
|
+
createTestReference(333),
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
for (const payload of payloads) {
|
|
359
|
+
await updater.update(payload, stamper)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Verify content integrity
|
|
363
|
+
const result = await finder.findAt(0n, 0n)
|
|
364
|
+
expect(result.current).toBe(2n)
|
|
365
|
+
expect(result.next).toBe(3n)
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it("should handle sequential updates with 64-byte references", async () => {
|
|
369
|
+
const beeClient = bee as unknown as Bee
|
|
370
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
371
|
+
const owner = updater.getOwner()
|
|
372
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
373
|
+
|
|
374
|
+
// Mix of 32-byte and 64-byte references
|
|
375
|
+
await updater.update(createTestReference64(1), stamper)
|
|
376
|
+
await updater.update(createTestReference(2), stamper)
|
|
377
|
+
await updater.update(createTestReference64(3), stamper)
|
|
378
|
+
|
|
379
|
+
const result = await finder.findAt(0n, 0n)
|
|
380
|
+
expect(result.current).toBe(2n)
|
|
381
|
+
expect(result.next).toBe(3n)
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
describe("Index Lookups", () => {
|
|
386
|
+
it("should find latest update when multiple exist", async () => {
|
|
387
|
+
const beeClient = bee as unknown as Bee
|
|
388
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
389
|
+
const owner = updater.getOwner()
|
|
390
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
391
|
+
|
|
392
|
+
// Write 5 updates
|
|
393
|
+
for (let i = 0; i < 5; i++) {
|
|
394
|
+
await updater.update(createTestReference(i), stamper)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Finder should find the latest (index 4)
|
|
398
|
+
const result = await finder.findAt(0n, 0n)
|
|
399
|
+
expect(result.current).toBe(4n)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
it("should return undefined for non-existent feed", async () => {
|
|
403
|
+
const beeClient = bee as unknown as Bee
|
|
404
|
+
// Different topic with no updates
|
|
405
|
+
const emptyTopic = createTestTopic("empty-topic")
|
|
406
|
+
const owner = signer.publicKey().address()
|
|
407
|
+
const finder = new SyncSequentialFinder(beeClient, emptyTopic, owner)
|
|
408
|
+
|
|
409
|
+
const result = await finder.findAt(0n, 0n)
|
|
410
|
+
expect(result.current).toBeUndefined()
|
|
411
|
+
expect(result.next).toBe(0n)
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it("should handle index 0 correctly", async () => {
|
|
415
|
+
const beeClient = bee as unknown as Bee
|
|
416
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
417
|
+
const owner = updater.getOwner()
|
|
418
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
419
|
+
|
|
420
|
+
// Single update at index 0
|
|
421
|
+
await updater.update(createTestReference(42), stamper)
|
|
422
|
+
|
|
423
|
+
const result = await finder.findAt(0n, 0n)
|
|
424
|
+
expect(result.current).toBe(0n)
|
|
425
|
+
expect(result.next).toBe(1n)
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it("should handle large index counts", { timeout: 15000 }, async () => {
|
|
429
|
+
const beeClient = bee as unknown as Bee
|
|
430
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
431
|
+
const owner = updater.getOwner()
|
|
432
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
433
|
+
|
|
434
|
+
// Write 100 updates to test scalability
|
|
435
|
+
const count = 100
|
|
436
|
+
for (let i = 0; i < count; i++) {
|
|
437
|
+
await updater.update(createTestReference(i), stamper)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const result = await finder.findAt(0n, 0n)
|
|
441
|
+
expect(result.current).toBe(BigInt(count - 1))
|
|
442
|
+
expect(result.next).toBe(BigInt(count))
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it("should correctly report next index for empty feed", async () => {
|
|
446
|
+
const beeClient = bee as unknown as Bee
|
|
447
|
+
const owner = signer.publicKey().address()
|
|
448
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
449
|
+
|
|
450
|
+
const result = await finder.findAt(0n, 0n)
|
|
451
|
+
expect(result.next).toBe(0n)
|
|
452
|
+
})
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
describe("Timestamp Handling", () => {
|
|
456
|
+
it("should ignore at parameter (sequential feeds are index-based)", async () => {
|
|
457
|
+
const beeClient = bee as unknown as Bee
|
|
458
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
459
|
+
const owner = updater.getOwner()
|
|
460
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
461
|
+
|
|
462
|
+
await updater.update(createTestReference(1), stamper)
|
|
463
|
+
await updater.update(createTestReference(2), stamper)
|
|
464
|
+
|
|
465
|
+
// at parameter should be ignored for sequential feeds
|
|
466
|
+
const result1 = await finder.findAt(0n, 0n)
|
|
467
|
+
const result2 = await finder.findAt(1000n, 0n)
|
|
468
|
+
const result3 = await finder.findAt(999999n, 0n)
|
|
469
|
+
|
|
470
|
+
expect(result1.current).toBe(1n)
|
|
471
|
+
expect(result2.current).toBe(1n)
|
|
472
|
+
expect(result3.current).toBe(1n)
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
it("should ignore after parameter (sequential feeds scan from 0)", async () => {
|
|
476
|
+
const beeClient = bee as unknown as Bee
|
|
477
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
478
|
+
const owner = updater.getOwner()
|
|
479
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
480
|
+
|
|
481
|
+
await updater.update(createTestReference(1), stamper)
|
|
482
|
+
await updater.update(createTestReference(2), stamper)
|
|
483
|
+
await updater.update(createTestReference(3), stamper)
|
|
484
|
+
|
|
485
|
+
// after parameter should be ignored for sequential feeds
|
|
486
|
+
const result1 = await finder.findAt(0n, 0n)
|
|
487
|
+
const result2 = await finder.findAt(0n, 100n)
|
|
488
|
+
|
|
489
|
+
expect(result1.current).toBe(2n)
|
|
490
|
+
expect(result2.current).toBe(2n)
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
it("should work with async finder using same parameters", async () => {
|
|
494
|
+
const beeClient = bee as unknown as Bee
|
|
495
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
496
|
+
const owner = updater.getOwner()
|
|
497
|
+
const asyncFinder = new AsyncSequentialFinder(beeClient, topic, owner)
|
|
498
|
+
|
|
499
|
+
await updater.update(createTestReference(1), stamper)
|
|
500
|
+
|
|
501
|
+
const result1 = await asyncFinder.findAt(0n, 0n)
|
|
502
|
+
const result2 = await asyncFinder.findAt(999n, 999n)
|
|
503
|
+
|
|
504
|
+
expect(result1.current).toBe(0n)
|
|
505
|
+
expect(result2.current).toBe(0n)
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
it("should handle queries on empty feed consistently", async () => {
|
|
509
|
+
const beeClient = bee as unknown as Bee
|
|
510
|
+
const owner = signer.publicKey().address()
|
|
511
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
512
|
+
|
|
513
|
+
const result1 = await finder.findAt(0n, 0n)
|
|
514
|
+
const result2 = await finder.findAt(100n, 50n)
|
|
515
|
+
|
|
516
|
+
expect(result1.current).toBeUndefined()
|
|
517
|
+
expect(result2.current).toBeUndefined()
|
|
518
|
+
expect(result1.next).toBe(0n)
|
|
519
|
+
expect(result2.next).toBe(0n)
|
|
520
|
+
})
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
describe("State Management", () => {
|
|
524
|
+
it("should track next index correctly", async () => {
|
|
525
|
+
const beeClient = bee as unknown as Bee
|
|
526
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
527
|
+
|
|
528
|
+
expect(updater.getState().nextIndex).toBe(0n)
|
|
529
|
+
|
|
530
|
+
await updater.update(createTestReference(1), stamper)
|
|
531
|
+
expect(updater.getState().nextIndex).toBe(1n)
|
|
532
|
+
|
|
533
|
+
await updater.update(createTestReference(2), stamper)
|
|
534
|
+
expect(updater.getState().nextIndex).toBe(2n)
|
|
535
|
+
|
|
536
|
+
await updater.update(createTestReference(3), stamper)
|
|
537
|
+
expect(updater.getState().nextIndex).toBe(3n)
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
it("should resume from saved state", async () => {
|
|
541
|
+
const beeClient = bee as unknown as Bee
|
|
542
|
+
const updater1 = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
543
|
+
|
|
544
|
+
// Write some updates
|
|
545
|
+
await updater1.update(createTestReference(1), stamper)
|
|
546
|
+
await updater1.update(createTestReference(2), stamper)
|
|
547
|
+
const state = updater1.getState()
|
|
548
|
+
|
|
549
|
+
// Create new updater and restore state
|
|
550
|
+
const updater2 = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
551
|
+
updater2.setState(state)
|
|
552
|
+
|
|
553
|
+
expect(updater2.getState().nextIndex).toBe(2n)
|
|
554
|
+
|
|
555
|
+
// Continue writing
|
|
556
|
+
await updater2.update(createTestReference(3), stamper)
|
|
557
|
+
expect(updater2.getState().nextIndex).toBe(3n)
|
|
558
|
+
|
|
559
|
+
// Verify all updates are findable
|
|
560
|
+
const finder = new SyncSequentialFinder(
|
|
561
|
+
beeClient,
|
|
562
|
+
topic,
|
|
563
|
+
updater2.getOwner(),
|
|
564
|
+
)
|
|
565
|
+
const result = await finder.findAt(0n, 0n)
|
|
566
|
+
expect(result.current).toBe(2n)
|
|
567
|
+
expect(result.next).toBe(3n)
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
it("should reset state correctly", async () => {
|
|
571
|
+
const beeClient = bee as unknown as Bee
|
|
572
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
573
|
+
|
|
574
|
+
// Write updates
|
|
575
|
+
await updater.update(createTestReference(1), stamper)
|
|
576
|
+
await updater.update(createTestReference(2), stamper)
|
|
577
|
+
expect(updater.getState().nextIndex).toBe(2n)
|
|
578
|
+
|
|
579
|
+
// Reset
|
|
580
|
+
updater.reset()
|
|
581
|
+
expect(updater.getState().nextIndex).toBe(0n)
|
|
582
|
+
|
|
583
|
+
// New writes should start from 0 (will overwrite previous)
|
|
584
|
+
await updater.update(createTestReference(100), stamper)
|
|
585
|
+
expect(updater.getState().nextIndex).toBe(1n)
|
|
586
|
+
})
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
describe("Error Handling", () => {
|
|
590
|
+
it("should handle network errors gracefully", async () => {
|
|
591
|
+
const failingStore = new MockChunkStore()
|
|
592
|
+
const failingBee = new FailingMockBee(failingStore, 0) // Always fail
|
|
593
|
+
const beeClient = failingBee as unknown as Bee
|
|
594
|
+
const owner = signer.publicKey().address()
|
|
595
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
596
|
+
|
|
597
|
+
// Should return undefined, not throw
|
|
598
|
+
const result = await finder.findAt(0n, 0n)
|
|
599
|
+
expect(result.current).toBeUndefined()
|
|
600
|
+
expect(result.next).toBe(0n)
|
|
601
|
+
})
|
|
602
|
+
|
|
603
|
+
it("should handle 404 for missing chunks", async () => {
|
|
604
|
+
const countingStore = new MockChunkStore()
|
|
605
|
+
const countingBee = new CountingMockBee(countingStore)
|
|
606
|
+
const beeClient = countingBee as unknown as Bee
|
|
607
|
+
const owner = signer.publicKey().address()
|
|
608
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
609
|
+
|
|
610
|
+
// Finder should return undefined, not crash
|
|
611
|
+
const result = await finder.findAt(0n, 0n)
|
|
612
|
+
expect(result.current).toBeUndefined()
|
|
613
|
+
expect(countingBee.downloadCalls).toBe(1) // Only tried index 0
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
it("should handle mixed error types", async () => {
|
|
617
|
+
const mixedStore = new MockChunkStore()
|
|
618
|
+
const mixedBee = new MixedErrorMockBee(mixedStore)
|
|
619
|
+
const beeClient = mixedBee as unknown as Bee
|
|
620
|
+
const owner = signer.publicKey().address()
|
|
621
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
622
|
+
|
|
623
|
+
// Should handle gracefully regardless of error type
|
|
624
|
+
const result = await finder.findAt(0n, 0n)
|
|
625
|
+
expect(result.current).toBeUndefined()
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
it("should continue finding after partial success", async () => {
|
|
629
|
+
const beeClient = bee as unknown as Bee
|
|
630
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
631
|
+
const owner = updater.getOwner()
|
|
632
|
+
|
|
633
|
+
// Create 3 updates
|
|
634
|
+
await updater.update(createTestReference(1), stamper)
|
|
635
|
+
await updater.update(createTestReference(2), stamper)
|
|
636
|
+
await updater.update(createTestReference(3), stamper)
|
|
637
|
+
|
|
638
|
+
// Now use counting bee to verify behavior
|
|
639
|
+
const countingBee = new CountingMockBee(store)
|
|
640
|
+
const finder = new SyncSequentialFinder(
|
|
641
|
+
countingBee as unknown as Bee,
|
|
642
|
+
topic,
|
|
643
|
+
owner,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
const result = await finder.findAt(0n, 0n)
|
|
647
|
+
expect(result.current).toBe(2n)
|
|
648
|
+
expect(result.next).toBe(3n)
|
|
649
|
+
// Should have probed indices 0, 1, 2, 3 (where 3 returns 404)
|
|
650
|
+
expect(countingBee.downloadCalls).toBe(4)
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
it("should handle empty payload gracefully", async () => {
|
|
654
|
+
const beeClient = bee as unknown as Bee
|
|
655
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
656
|
+
|
|
657
|
+
// Empty payloads should throw (data length 0 is invalid)
|
|
658
|
+
const emptyPayload = new Uint8Array(0)
|
|
659
|
+
await expect(updater.update(emptyPayload, stamper)).rejects.toThrow()
|
|
660
|
+
})
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
describe("Owner/Topic Isolation", () => {
|
|
664
|
+
it("should isolate updates by owner", async () => {
|
|
665
|
+
const beeClient = bee as unknown as Bee
|
|
666
|
+
|
|
667
|
+
// Two different signers (owners)
|
|
668
|
+
const signerA = createTestSigner()
|
|
669
|
+
const signerB = new PrivateKey(
|
|
670
|
+
"9a4ce1ef8d14b7864ea3f1ecfcb39f937ce4a45f47f4d7d02f6b76f1f3ab2c11",
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
const updaterA = new BasicSequentialUpdater(beeClient, topic, signerA)
|
|
674
|
+
const updaterB = new BasicSequentialUpdater(beeClient, topic, signerB)
|
|
675
|
+
|
|
676
|
+
// Write to both feeds
|
|
677
|
+
await updaterA.update(createTestReference(100), stamper)
|
|
678
|
+
await updaterA.update(createTestReference(101), stamper)
|
|
679
|
+
|
|
680
|
+
await updaterB.update(createTestReference(200), stamper)
|
|
681
|
+
|
|
682
|
+
// Each owner should see only their own updates
|
|
683
|
+
const finderA = new SyncSequentialFinder(
|
|
684
|
+
beeClient,
|
|
685
|
+
topic,
|
|
686
|
+
updaterA.getOwner(),
|
|
687
|
+
)
|
|
688
|
+
const finderB = new SyncSequentialFinder(
|
|
689
|
+
beeClient,
|
|
690
|
+
topic,
|
|
691
|
+
updaterB.getOwner(),
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
const resultA = await finderA.findAt(0n, 0n)
|
|
695
|
+
const resultB = await finderB.findAt(0n, 0n)
|
|
696
|
+
|
|
697
|
+
expect(resultA.current).toBe(1n) // Owner A has 2 updates (index 0, 1)
|
|
698
|
+
expect(resultB.current).toBe(0n) // Owner B has 1 update (index 0)
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
it("should isolate updates by topic", async () => {
|
|
702
|
+
const beeClient = bee as unknown as Bee
|
|
703
|
+
|
|
704
|
+
const topicA = createTestTopic("topic-a")
|
|
705
|
+
const topicB = createTestTopic("topic-b")
|
|
706
|
+
|
|
707
|
+
const updaterA = new BasicSequentialUpdater(beeClient, topicA, signer)
|
|
708
|
+
const updaterB = new BasicSequentialUpdater(beeClient, topicB, signer)
|
|
709
|
+
|
|
710
|
+
// Write different amounts to each topic
|
|
711
|
+
await updaterA.update(createTestReference(1), stamper)
|
|
712
|
+
await updaterA.update(createTestReference(2), stamper)
|
|
713
|
+
await updaterA.update(createTestReference(3), stamper)
|
|
714
|
+
|
|
715
|
+
await updaterB.update(createTestReference(10), stamper)
|
|
716
|
+
|
|
717
|
+
const owner = signer.publicKey().address()
|
|
718
|
+
const finderA = new SyncSequentialFinder(beeClient, topicA, owner)
|
|
719
|
+
const finderB = new SyncSequentialFinder(beeClient, topicB, owner)
|
|
720
|
+
|
|
721
|
+
const resultA = await finderA.findAt(0n, 0n)
|
|
722
|
+
const resultB = await finderB.findAt(0n, 0n)
|
|
723
|
+
|
|
724
|
+
expect(resultA.current).toBe(2n) // Topic A has 3 updates
|
|
725
|
+
expect(resultB.current).toBe(0n) // Topic B has 1 update
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
it("should handle cross-owner lookup correctly", async () => {
|
|
729
|
+
const beeClient = bee as unknown as Bee
|
|
730
|
+
|
|
731
|
+
const signerA = createTestSigner()
|
|
732
|
+
const signerB = new PrivateKey(
|
|
733
|
+
"7f6c8f5de489c56ba40b494a26d0c6dd0c05fc4f0d37fe2f217af6e9ac7b1a01",
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
const updaterA = new BasicSequentialUpdater(beeClient, topic, signerA)
|
|
737
|
+
|
|
738
|
+
// Owner A writes updates
|
|
739
|
+
await updaterA.update(createTestReference(1), stamper)
|
|
740
|
+
await updaterA.update(createTestReference(2), stamper)
|
|
741
|
+
|
|
742
|
+
// Try to find using Owner B's address - should find nothing
|
|
743
|
+
const finderB = new SyncSequentialFinder(
|
|
744
|
+
beeClient,
|
|
745
|
+
topic,
|
|
746
|
+
signerB.publicKey().address(),
|
|
747
|
+
)
|
|
748
|
+
const result = await finderB.findAt(0n, 0n)
|
|
749
|
+
|
|
750
|
+
expect(result.current).toBeUndefined()
|
|
751
|
+
expect(result.next).toBe(0n)
|
|
752
|
+
})
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
describe("Concurrent Operations", () => {
|
|
756
|
+
it("should handle concurrent reads safely", async () => {
|
|
757
|
+
const beeClient = bee as unknown as Bee
|
|
758
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
759
|
+
const owner = updater.getOwner()
|
|
760
|
+
|
|
761
|
+
// Write some updates
|
|
762
|
+
for (let i = 0; i < 10; i++) {
|
|
763
|
+
await updater.update(createTestReference(i), stamper)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Create multiple finders and read concurrently
|
|
767
|
+
const finders = [
|
|
768
|
+
new SyncSequentialFinder(beeClient, topic, owner),
|
|
769
|
+
new SyncSequentialFinder(beeClient, topic, owner),
|
|
770
|
+
new AsyncSequentialFinder(beeClient, topic, owner),
|
|
771
|
+
new AsyncSequentialFinder(beeClient, topic, owner),
|
|
772
|
+
]
|
|
773
|
+
|
|
774
|
+
const results = await Promise.all(
|
|
775
|
+
finders.map((finder) => finder.findAt(0n, 0n)),
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
// All should return the same result
|
|
779
|
+
for (const result of results) {
|
|
780
|
+
expect(result.current).toBe(9n)
|
|
781
|
+
expect(result.next).toBe(10n)
|
|
782
|
+
}
|
|
783
|
+
})
|
|
784
|
+
|
|
785
|
+
it("should handle rapid sequential updates", async () => {
|
|
786
|
+
const beeClient = bee as unknown as Bee
|
|
787
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
788
|
+
const owner = updater.getOwner()
|
|
789
|
+
|
|
790
|
+
// Rapid sequential updates (must be sequential to avoid race conditions)
|
|
791
|
+
// Sequential feeds require sequential writes since nextIndex is incremented after each update
|
|
792
|
+
for (let i = 0; i < 20; i++) {
|
|
793
|
+
await updater.update(createTestReference(i), stamper)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Verify all updates succeeded
|
|
797
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
798
|
+
const result = await finder.findAt(0n, 0n)
|
|
799
|
+
|
|
800
|
+
expect(result.current).toBe(19n)
|
|
801
|
+
expect(result.next).toBe(20n)
|
|
802
|
+
expect(updater.getState().nextIndex).toBe(20n)
|
|
803
|
+
})
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
describe("Performance", () => {
|
|
807
|
+
it(
|
|
808
|
+
"should complete linear scan within reasonable bounds",
|
|
809
|
+
{ timeout: 30000 },
|
|
810
|
+
async () => {
|
|
811
|
+
const countingStore = new MockChunkStore()
|
|
812
|
+
const countingBee = new CountingMockBee(countingStore)
|
|
813
|
+
const beeClient = countingBee as unknown as Bee
|
|
814
|
+
|
|
815
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
816
|
+
|
|
817
|
+
// Write 100 updates
|
|
818
|
+
const updateCount = 100
|
|
819
|
+
for (let i = 0; i < updateCount; i++) {
|
|
820
|
+
await updater.update(createTestReference(i), stamper)
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Reset counter before find
|
|
824
|
+
countingBee.downloadCalls = 0
|
|
825
|
+
|
|
826
|
+
const finder = new SyncSequentialFinder(
|
|
827
|
+
beeClient,
|
|
828
|
+
topic,
|
|
829
|
+
updater.getOwner(),
|
|
830
|
+
)
|
|
831
|
+
const result = await finder.findAt(0n, 0n)
|
|
832
|
+
|
|
833
|
+
expect(result.current).toBe(BigInt(updateCount - 1))
|
|
834
|
+
// Linear scan should probe exactly updateCount + 1 times (0 to 100 inclusive, where 100 is 404)
|
|
835
|
+
expect(countingBee.downloadCalls).toBe(updateCount + 1)
|
|
836
|
+
},
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
it(
|
|
840
|
+
"sync and async finders should match for large datasets",
|
|
841
|
+
{ timeout: 10000 },
|
|
842
|
+
async () => {
|
|
843
|
+
const beeClient = bee as unknown as Bee
|
|
844
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
845
|
+
const owner = updater.getOwner()
|
|
846
|
+
|
|
847
|
+
// Write 50 updates
|
|
848
|
+
for (let i = 0; i < 50; i++) {
|
|
849
|
+
await updater.update(createTestReference(i), stamper)
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const syncFinder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
853
|
+
const asyncFinder = new AsyncSequentialFinder(beeClient, topic, owner)
|
|
854
|
+
|
|
855
|
+
const syncResult = await syncFinder.findAt(0n, 0n)
|
|
856
|
+
const asyncResult = await asyncFinder.findAt(0n, 0n)
|
|
857
|
+
|
|
858
|
+
expect(asyncResult.current).toBe(syncResult.current)
|
|
859
|
+
expect(asyncResult.next).toBe(syncResult.next)
|
|
860
|
+
expect(syncResult.current).toBe(49n)
|
|
861
|
+
},
|
|
862
|
+
)
|
|
863
|
+
})
|
|
864
|
+
|
|
865
|
+
describe("Edge Cases", () => {
|
|
866
|
+
it("should handle single update correctly", async () => {
|
|
867
|
+
const beeClient = bee as unknown as Bee
|
|
868
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
869
|
+
const owner = updater.getOwner()
|
|
870
|
+
|
|
871
|
+
await updater.update(createTestReference(42), stamper)
|
|
872
|
+
|
|
873
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
874
|
+
const result = await finder.findAt(0n, 0n)
|
|
875
|
+
|
|
876
|
+
expect(result.current).toBe(0n)
|
|
877
|
+
expect(result.next).toBe(1n)
|
|
878
|
+
})
|
|
879
|
+
|
|
880
|
+
it("should handle maximum practical index", async () => {
|
|
881
|
+
const beeClient = bee as unknown as Bee
|
|
882
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
883
|
+
|
|
884
|
+
// Start from a high index
|
|
885
|
+
updater.setState({ nextIndex: 1000000n })
|
|
886
|
+
|
|
887
|
+
await updater.update(createTestReference(1), stamper)
|
|
888
|
+
expect(updater.getState().nextIndex).toBe(1000001n)
|
|
889
|
+
|
|
890
|
+
await updater.update(createTestReference(2), stamper)
|
|
891
|
+
expect(updater.getState().nextIndex).toBe(1000002n)
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
it("should preserve reference integrity through update cycle", async () => {
|
|
895
|
+
const beeClient = bee as unknown as Bee
|
|
896
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
897
|
+
|
|
898
|
+
const ref32 = createTestReference(123)
|
|
899
|
+
const ref64 = createTestReference64(456)
|
|
900
|
+
|
|
901
|
+
const socAddress1 = await updater.update(ref32, stamper)
|
|
902
|
+
const socAddress2 = await updater.update(ref64, stamper)
|
|
903
|
+
|
|
904
|
+
// SOC addresses should be valid 32-byte addresses
|
|
905
|
+
expect(socAddress1).toHaveLength(32)
|
|
906
|
+
expect(socAddress2).toHaveLength(32)
|
|
907
|
+
expect(socAddress1).not.toEqual(socAddress2)
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
it("should handle getOwner correctly", async () => {
|
|
911
|
+
const beeClient = bee as unknown as Bee
|
|
912
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
913
|
+
|
|
914
|
+
const owner = updater.getOwner()
|
|
915
|
+
const expectedOwner = signer.publicKey().address()
|
|
916
|
+
|
|
917
|
+
expect(owner.toHex()).toBe(expectedOwner.toHex())
|
|
918
|
+
})
|
|
919
|
+
})
|
|
920
|
+
|
|
921
|
+
describe("Finder Consistency", () => {
|
|
922
|
+
it("should return consistent results across multiple queries", async () => {
|
|
923
|
+
const beeClient = bee as unknown as Bee
|
|
924
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
925
|
+
const owner = updater.getOwner()
|
|
926
|
+
|
|
927
|
+
await updater.update(createTestReference(1), stamper)
|
|
928
|
+
await updater.update(createTestReference(2), stamper)
|
|
929
|
+
await updater.update(createTestReference(3), stamper)
|
|
930
|
+
|
|
931
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
932
|
+
|
|
933
|
+
// Query multiple times
|
|
934
|
+
const results = await Promise.all([
|
|
935
|
+
finder.findAt(0n, 0n),
|
|
936
|
+
finder.findAt(0n, 0n),
|
|
937
|
+
finder.findAt(0n, 0n),
|
|
938
|
+
])
|
|
939
|
+
|
|
940
|
+
for (const result of results) {
|
|
941
|
+
expect(result.current).toBe(2n)
|
|
942
|
+
expect(result.next).toBe(3n)
|
|
943
|
+
}
|
|
944
|
+
})
|
|
945
|
+
|
|
946
|
+
it("should handle interleaved reads and writes", async () => {
|
|
947
|
+
const beeClient = bee as unknown as Bee
|
|
948
|
+
const updater = new BasicSequentialUpdater(beeClient, topic, signer)
|
|
949
|
+
const owner = updater.getOwner()
|
|
950
|
+
const finder = new SyncSequentialFinder(beeClient, topic, owner)
|
|
951
|
+
|
|
952
|
+
// Write, read, write, read pattern
|
|
953
|
+
await updater.update(createTestReference(1), stamper)
|
|
954
|
+
const result1 = await finder.findAt(0n, 0n)
|
|
955
|
+
expect(result1.current).toBe(0n)
|
|
956
|
+
|
|
957
|
+
await updater.update(createTestReference(2), stamper)
|
|
958
|
+
const result2 = await finder.findAt(0n, 0n)
|
|
959
|
+
expect(result2.current).toBe(1n)
|
|
960
|
+
|
|
961
|
+
await updater.update(createTestReference(3), stamper)
|
|
962
|
+
const result3 = await finder.findAt(0n, 0n)
|
|
963
|
+
expect(result3.current).toBe(2n)
|
|
964
|
+
})
|
|
965
|
+
})
|
|
966
|
+
})
|