@novasamatech/statement-store 0.8.10 → 0.8.12
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.
|
@@ -263,7 +263,7 @@ describe('session', () => {
|
|
|
263
263
|
const { session } = makeSession({ peer: [peerRequest] });
|
|
264
264
|
await delay();
|
|
265
265
|
const result = await session.submitResponseMessage(requestId, 'success');
|
|
266
|
-
expect(result
|
|
266
|
+
await expect(result).toBeOk();
|
|
267
267
|
});
|
|
268
268
|
it('treats an incoming request as already answered when our response is present (no resubmit)', async () => {
|
|
269
269
|
const requestId = 'peer-request-id';
|
|
@@ -274,7 +274,7 @@ describe('session', () => {
|
|
|
274
274
|
await delay();
|
|
275
275
|
const submitsBefore = adapter.submitStatement.mock.calls.length;
|
|
276
276
|
const result = await session.submitResponseMessage(requestId, 'success');
|
|
277
|
-
expect(result
|
|
277
|
+
await expect(result).toBeOk();
|
|
278
278
|
expect(adapter.submitStatement.mock.calls.length).toBe(submitsBefore); // no new submit
|
|
279
279
|
});
|
|
280
280
|
it('does not re-deliver an incoming request we already answered', async () => {
|
|
@@ -365,8 +365,7 @@ describe('session', () => {
|
|
|
365
365
|
});
|
|
366
366
|
await delay();
|
|
367
367
|
const result = await responsePromise;
|
|
368
|
-
expect(result.
|
|
369
|
-
expect(result.unwrapOr({ responseCode: 'unknown' }).responseCode).toBe('success');
|
|
368
|
+
await expect(result).toBeOkWith(expect.objectContaining({ responseCode: 'success' }));
|
|
370
369
|
});
|
|
371
370
|
it('does not resend a message that is already in flight (dedup)', async () => {
|
|
372
371
|
const store = createInMemoryStatementStore();
|
|
@@ -401,8 +400,8 @@ describe('session', () => {
|
|
|
401
400
|
isComplete: true,
|
|
402
401
|
});
|
|
403
402
|
await delay();
|
|
404
|
-
expect(
|
|
405
|
-
expect(
|
|
403
|
+
await expect(w1).toBeOk();
|
|
404
|
+
await expect(w2).toBeOk();
|
|
406
405
|
});
|
|
407
406
|
it('preserves FIFO order: a later fitting message does not overtake queued ones', async () => {
|
|
408
407
|
const m1 = rawCodec.enc(new Uint8Array([1, 2, 3])); // first → in-flight batch
|
|
@@ -426,7 +425,7 @@ describe('session', () => {
|
|
|
426
425
|
await delay();
|
|
427
426
|
adapter.submitStatement.mockClear();
|
|
428
427
|
const result = await session.submitRequestMessage(rawCodec, new Uint8Array([1, 2, 3, 4]));
|
|
429
|
-
expect(result
|
|
428
|
+
await expect(result).toBeErr();
|
|
430
429
|
expect(adapter.submitStatement).not.toHaveBeenCalled();
|
|
431
430
|
});
|
|
432
431
|
});
|
|
@@ -524,7 +523,7 @@ describe('session', () => {
|
|
|
524
523
|
const { session } = makeSession({ peer: [peerRequest] });
|
|
525
524
|
await delay(); // init buffers the peer request
|
|
526
525
|
const result = await session.waitForRequestMessage(rawCodec, () => 'matched');
|
|
527
|
-
expect(result
|
|
526
|
+
await expect(result).toBeOkWith('matched');
|
|
528
527
|
}, 2000);
|
|
529
528
|
it('opens a single subscription on the incoming topic', () => {
|
|
530
529
|
const store = createInMemoryStatementStore();
|
|
@@ -591,7 +590,7 @@ describe('session', () => {
|
|
|
591
590
|
const { session } = makeSession();
|
|
592
591
|
await delay();
|
|
593
592
|
const result = await session.submitResponseMessage('wrong-id', 'success');
|
|
594
|
-
expect(result
|
|
593
|
+
await expect(result).toBeErr();
|
|
595
594
|
});
|
|
596
595
|
it('NACKs an undecodable incoming request with decodingFailed', async () => {
|
|
597
596
|
// A request (enum tag 0) whose requestId decodes but whose data vector claims more
|
|
@@ -650,7 +649,7 @@ describe('session', () => {
|
|
|
650
649
|
expect(adapter.submitStatement).not.toHaveBeenCalled(); // no premature NACK
|
|
651
650
|
// The legitimate response still goes through.
|
|
652
651
|
const res = await session.submitResponseMessage(reqId, 'success');
|
|
653
|
-
expect(res
|
|
652
|
+
await expect(res).toBeOk();
|
|
654
653
|
const decoded = StatementData.dec(lastSubmitted(adapter).data);
|
|
655
654
|
expect(decoded.tag === 'response' && decoded.value.responseCode).toBe('success');
|
|
656
655
|
});
|
|
@@ -676,7 +675,7 @@ describe('session', () => {
|
|
|
676
675
|
host.respondToRequests(RemoteMsg, () => 'success');
|
|
677
676
|
await settle();
|
|
678
677
|
const ack = await peer.request(RemoteMsg, requestMsg('p1'));
|
|
679
|
-
expect(ack
|
|
678
|
+
await expect(ack).toBeOk();
|
|
680
679
|
host.dispose();
|
|
681
680
|
peer.dispose();
|
|
682
681
|
});
|
|
@@ -688,7 +687,7 @@ describe('session', () => {
|
|
|
688
687
|
host.respondToRequests(RemoteMsg, () => okAsync('decodingFailed'));
|
|
689
688
|
await settle();
|
|
690
689
|
const ack = await peer.request(RemoteMsg, requestMsg('p1'));
|
|
691
|
-
expect(ack
|
|
690
|
+
await expect(ack).toBeErr();
|
|
692
691
|
expect(ack._unsafeUnwrapErr()).toBeInstanceOf(DecodingError);
|
|
693
692
|
host.dispose();
|
|
694
693
|
peer.dispose();
|
|
@@ -701,7 +700,7 @@ describe('session', () => {
|
|
|
701
700
|
host.respondToRequests(RemoteMsg, () => errAsync(new Error('handler boom')));
|
|
702
701
|
await settle();
|
|
703
702
|
const ack = await peer.request(RemoteMsg, requestMsg('p1'));
|
|
704
|
-
expect(ack
|
|
703
|
+
await expect(ack).toBeErr();
|
|
705
704
|
expect(ack._unsafeUnwrapErr()).toBeInstanceOf(UnknownError);
|
|
706
705
|
host.dispose();
|
|
707
706
|
peer.dispose();
|
|
@@ -731,8 +730,8 @@ describe('session', () => {
|
|
|
731
730
|
await delay();
|
|
732
731
|
const resA = await session.submitResponseMessage('A', 'success');
|
|
733
732
|
const resB = await session.submitResponseMessage('B', 'success');
|
|
734
|
-
expect(resA
|
|
735
|
-
expect(resB
|
|
733
|
+
await expect(resA).toBeOk();
|
|
734
|
+
await expect(resB).toBeOk();
|
|
736
735
|
});
|
|
737
736
|
it('remains answerable after response submission retries are exhausted', async () => {
|
|
738
737
|
const peerRequest = makeStatement({ tag: 'request', value: { requestId: 'rid', data: [] } });
|
|
@@ -740,11 +739,11 @@ describe('session', () => {
|
|
|
740
739
|
await delay();
|
|
741
740
|
adapter.submitStatement.mockReturnValue(errAsync(new Error('store rejected')));
|
|
742
741
|
const first = await session.submitResponseMessage('rid', 'success'); // all retries fail → err + rollback
|
|
743
|
-
expect(first
|
|
742
|
+
await expect(first).toBeErr();
|
|
744
743
|
adapter.submitStatement.mockReturnValue(okAsync(undefined)); // store recovers
|
|
745
744
|
const submitsBefore = adapter.submitStatement.mock.calls.length;
|
|
746
745
|
const second = await session.submitResponseMessage('rid', 'success'); // retryable → submits and succeeds
|
|
747
|
-
expect(second
|
|
746
|
+
await expect(second).toBeOk();
|
|
748
747
|
expect(adapter.submitStatement.mock.calls.length).toBeGreaterThan(submitsBefore);
|
|
749
748
|
}, 3000);
|
|
750
749
|
it.each([
|
|
@@ -774,13 +773,13 @@ describe('session', () => {
|
|
|
774
773
|
pendings.find(p => p.requestId === 'A').settle(err(new PriorityError(0n, 0n))); // A lands late, rejected
|
|
775
774
|
const resA = await resAPromise;
|
|
776
775
|
const resB = await resBPromise;
|
|
777
|
-
expect(resB
|
|
778
|
-
expect(resA
|
|
776
|
+
await expect(resB).toBeOk();
|
|
777
|
+
await expect(resA).toBeOk(); // superseded rejection absorbed, not surfaced as an error
|
|
779
778
|
// A stays answered: re-answering it must NOT submit again (which would clobber B's response).
|
|
780
779
|
const submitsBefore = adapter.submitStatement.mock.calls.length;
|
|
781
780
|
const reAnswer = await session.submitResponseMessage('A', 'success');
|
|
782
781
|
await delay();
|
|
783
|
-
expect(reAnswer
|
|
782
|
+
await expect(reAnswer).toBeOk();
|
|
784
783
|
expect(adapter.submitStatement.mock.calls.length).toBe(submitsBefore); // deduped → no resubmit
|
|
785
784
|
session.dispose();
|
|
786
785
|
}, 3000);
|
|
@@ -793,7 +792,7 @@ describe('session', () => {
|
|
|
793
792
|
await delay();
|
|
794
793
|
const before = adapter.submitStatement.mock.calls.length;
|
|
795
794
|
const result = await session.clearOutgoingStatement();
|
|
796
|
-
expect(result
|
|
795
|
+
await expect(result).toBeOk();
|
|
797
796
|
expect(adapter.submitStatement.mock.calls.length).toBe(before);
|
|
798
797
|
});
|
|
799
798
|
it('absorbs an ExpiryTooLow on the superseding empty batch as success', async () => {
|
|
@@ -809,7 +808,7 @@ describe('session', () => {
|
|
|
809
808
|
void session.submitRequestMessage(rawCodec, new Uint8Array([1]));
|
|
810
809
|
await delay();
|
|
811
810
|
const cleared = await session.clearOutgoingStatement();
|
|
812
|
-
expect(cleared
|
|
811
|
+
await expect(cleared).toBeOk(); // ExpiryTooLow suppressed
|
|
813
812
|
session.dispose();
|
|
814
813
|
}, 3000);
|
|
815
814
|
it('submits an empty batch on the same channel at a higher expiry and clears local state', async () => {
|
|
@@ -823,7 +822,7 @@ describe('session', () => {
|
|
|
823
822
|
if (liveDecoded.tag === 'request')
|
|
824
823
|
expect(liveDecoded.value.data.length).toBe(1);
|
|
825
824
|
const result = await session.clearOutgoingStatement();
|
|
826
|
-
expect(result
|
|
825
|
+
await expect(result).toBeOk();
|
|
827
826
|
const clearCall = adapter.submitStatement.mock.calls.at(-1)?.[0];
|
|
828
827
|
const clearDecoded = StatementData.dec(clearCall.data);
|
|
829
828
|
expect(clearDecoded.tag).toBe('request');
|
|
@@ -848,7 +847,7 @@ describe('session', () => {
|
|
|
848
847
|
void session.submitRequestMessage(rawCodec, new Uint8Array([1, 2, 3]));
|
|
849
848
|
await settle();
|
|
850
849
|
const result = await session.clearOutgoingStatement();
|
|
851
|
-
expect(result
|
|
850
|
+
await expect(result).toBeOk();
|
|
852
851
|
await settle();
|
|
853
852
|
const requests = store
|
|
854
853
|
.currentStatements()
|
|
@@ -862,12 +861,12 @@ describe('session', () => {
|
|
|
862
861
|
const { session } = makeSession();
|
|
863
862
|
await delay();
|
|
864
863
|
const submit = await session.submitRequestMessage(rawCodec, new Uint8Array([9]));
|
|
865
|
-
expect(submit
|
|
864
|
+
await expect(submit).toBeOk();
|
|
866
865
|
const requestId = submit._unsafeUnwrap().requestId;
|
|
867
866
|
const waiter = session.waitForResponseMessage(requestId);
|
|
868
867
|
await session.clearOutgoingStatement();
|
|
869
868
|
const waited = await waiter;
|
|
870
|
-
expect(waited
|
|
869
|
+
await expect(waited).toBeErr();
|
|
871
870
|
});
|
|
872
871
|
it('clears local state and rejects waiters even when the supersede submission fails', async () => {
|
|
873
872
|
const { session, adapter } = makeSession();
|
|
@@ -877,10 +876,10 @@ describe('session', () => {
|
|
|
877
876
|
const waiter = session.waitForResponseMessage(requestId);
|
|
878
877
|
adapter.submitStatement.mockReturnValueOnce(errAsync(new Error('store rejected')));
|
|
879
878
|
const result = await session.clearOutgoingStatement();
|
|
880
|
-
expect(result
|
|
879
|
+
await expect(result).toBeErr();
|
|
881
880
|
// The pending waiter is rejected despite the failed submission.
|
|
882
881
|
const waited = await waiter;
|
|
883
|
-
expect(waited
|
|
882
|
+
await expect(waited).toBeErr();
|
|
884
883
|
// State is cleared: the next message starts a brand-new batch (data length 1, not 2).
|
|
885
884
|
adapter.submitStatement.mockReturnValue(okAsync(undefined));
|
|
886
885
|
void session.submitRequestMessage(rawCodec, new Uint8Array([4]));
|
|
@@ -902,10 +901,10 @@ describe('session', () => {
|
|
|
902
901
|
const waiter = session.waitForResponseMessage(requestId);
|
|
903
902
|
const submitsBefore = adapter.submitStatement.mock.calls.length;
|
|
904
903
|
const result = await session.clearOutgoingStatement();
|
|
905
|
-
expect(result
|
|
904
|
+
await expect(result).toBeOk();
|
|
906
905
|
// The queued waiter is rejected rather than left to be submitted after init.
|
|
907
906
|
const waited = await waiter;
|
|
908
|
-
expect(waited
|
|
907
|
+
await expect(waited).toBeErr();
|
|
909
908
|
// No empty batch is submitted since there was no live on-chain request yet.
|
|
910
909
|
expect(adapter.submitStatement.mock.calls.length).toBe(submitsBefore);
|
|
911
910
|
});
|
|
@@ -1011,7 +1010,7 @@ describe('session', () => {
|
|
|
1011
1010
|
const submit = await session.submitRequestMessage(rawCodec, new Uint8Array([1]));
|
|
1012
1011
|
const requestId = submit._unsafeUnwrap().requestId;
|
|
1013
1012
|
const waited = await session.waitForResponseMessage(requestId);
|
|
1014
|
-
expect(waited
|
|
1013
|
+
await expect(waited).toBeErr();
|
|
1015
1014
|
}, 2000);
|
|
1016
1015
|
it('absorbs a superseded older submission rejected as ExpiryTooLow without surfacing an error', async () => {
|
|
1017
1016
|
// Two messages batch onto one outgoing request: the first submission (requestId A) is in
|
|
@@ -1055,7 +1054,7 @@ describe('session', () => {
|
|
|
1055
1054
|
const waiter = session.waitForRequestMessage(rawCodec, () => 'x');
|
|
1056
1055
|
session.dispose();
|
|
1057
1056
|
const result = await waiter;
|
|
1058
|
-
expect(result
|
|
1057
|
+
await expect(result).toBeErr();
|
|
1059
1058
|
}, 2000);
|
|
1060
1059
|
it('cancels a pending init retry (no further queries)', async () => {
|
|
1061
1060
|
const queryStatements = vi.fn(() => errAsync(new Error('store down'))); // init always fails → schedules retry
|
|
@@ -1071,7 +1070,7 @@ describe('session', () => {
|
|
|
1071
1070
|
await delay();
|
|
1072
1071
|
session.dispose();
|
|
1073
1072
|
const result = await session.submitRequestMessage(rawCodec, new Uint8Array([1]));
|
|
1074
|
-
expect(result
|
|
1073
|
+
await expect(result).toBeErr(); // surfaced immediately, not a token left pending forever
|
|
1075
1074
|
expect(adapter.submitStatement).not.toHaveBeenCalled();
|
|
1076
1075
|
});
|
|
1077
1076
|
it('rejects submitResponseMessage after dispose', async () => {
|
|
@@ -1079,7 +1078,7 @@ describe('session', () => {
|
|
|
1079
1078
|
await delay();
|
|
1080
1079
|
session.dispose();
|
|
1081
1080
|
const result = await session.submitResponseMessage('any-id', 'success');
|
|
1082
|
-
expect(result
|
|
1081
|
+
await expect(result).toBeErr();
|
|
1083
1082
|
});
|
|
1084
1083
|
it('does not re-activate when disposed while init is in flight', async () => {
|
|
1085
1084
|
// dispose() lands during init's query await; init must bail before restoring state / flipping
|
|
@@ -1089,13 +1088,13 @@ describe('session', () => {
|
|
|
1089
1088
|
const queryStatements = vi.fn(() => ResultAsync.fromSafePromise(gate));
|
|
1090
1089
|
const { session, adapter } = makeSession({ queryStatements });
|
|
1091
1090
|
const queued = await session.submitRequestMessage(rawCodec, new Uint8Array([1])); // queued during init
|
|
1092
|
-
expect(queued
|
|
1091
|
+
await expect(queued).toBeOk();
|
|
1093
1092
|
session.dispose(); // dispose mid-init
|
|
1094
1093
|
resolveQueries([]); // init resumes — must bail before draining the queue / activating
|
|
1095
1094
|
await settle();
|
|
1096
1095
|
expect(adapter.submitStatement).not.toHaveBeenCalled(); // no resurrection-driven submit
|
|
1097
1096
|
const after = await session.submitRequestMessage(rawCodec, new Uint8Array([2]));
|
|
1098
|
-
expect(after
|
|
1097
|
+
await expect(after).toBeErr(); // session stays disposed
|
|
1099
1098
|
}, 3000);
|
|
1100
1099
|
});
|
|
1101
1100
|
// The in-memory adapter replicates the store's observable contract; `fidelity` pins the double's
|
|
@@ -1123,7 +1122,7 @@ describe('session', () => {
|
|
|
1123
1122
|
const store = createInMemoryStatementStore();
|
|
1124
1123
|
await store.submitStatement(makeSignedStatement(hex(0xaa), 10n, hex(0x01), new Uint8Array([1])));
|
|
1125
1124
|
const higher = await store.submitStatement(makeSignedStatement(hex(0xaa), 11n, hex(0x01), new Uint8Array([2])));
|
|
1126
|
-
expect(higher
|
|
1125
|
+
await expect(higher).toBeOk();
|
|
1127
1126
|
expect(store.currentStatements()).toHaveLength(1);
|
|
1128
1127
|
expect(store.currentStatements()[0]?.expiry).toBe(11n);
|
|
1129
1128
|
});
|
|
@@ -1132,9 +1131,9 @@ describe('session', () => {
|
|
|
1132
1131
|
await store.submitStatement(makeSignedStatement(hex(0xaa), 10n, hex(0x01), new Uint8Array([1])));
|
|
1133
1132
|
const equal = await store.submitStatement(makeSignedStatement(hex(0xaa), 10n, hex(0x01), new Uint8Array([9])));
|
|
1134
1133
|
const lower = await store.submitStatement(makeSignedStatement(hex(0xaa), 5n, hex(0x01), new Uint8Array([9])));
|
|
1135
|
-
expect(equal
|
|
1134
|
+
await expect(equal).toBeErr();
|
|
1136
1135
|
expect(equal._unsafeUnwrapErr()).toBeInstanceOf(ExpiryTooLowError);
|
|
1137
|
-
expect(lower
|
|
1136
|
+
await expect(lower).toBeErr();
|
|
1138
1137
|
// The original statement is untouched.
|
|
1139
1138
|
expect(store.currentStatements()[0]?.data).toEqual(new Uint8Array([1]));
|
|
1140
1139
|
});
|
|
@@ -1143,7 +1142,7 @@ describe('session', () => {
|
|
|
1143
1142
|
const stmt = makeSignedStatement(hex(0xaa), 10n, hex(0x01), new Uint8Array([1]));
|
|
1144
1143
|
await store.submitStatement(stmt);
|
|
1145
1144
|
const again = await store.submitStatement(stmt);
|
|
1146
|
-
expect(again
|
|
1145
|
+
await expect(again).toBeOk();
|
|
1147
1146
|
expect(store.currentStatements()).toHaveLength(1);
|
|
1148
1147
|
});
|
|
1149
1148
|
it('coexists statements on different channels sharing a topic', async () => {
|
|
@@ -1184,9 +1183,9 @@ describe('session', () => {
|
|
|
1184
1183
|
const mobileReplyAck = mobileGotRequest.andThen(req => mobile.request(RemoteMsg, { id: 'm1', kind: 'reply', respondingTo: req.id, body: 'signature' }));
|
|
1185
1184
|
const hostReply = host.waitForRequestMessage(RemoteMsg, msg => msg.kind === 'reply' && msg.respondingTo === 'h1' ? msg.body : undefined);
|
|
1186
1185
|
await settle();
|
|
1187
|
-
expect(
|
|
1188
|
-
expect(
|
|
1189
|
-
expect(
|
|
1186
|
+
await expect(hostAck).toBeOk(); // mobile ACKed the host request
|
|
1187
|
+
await expect(mobileReplyAck).toBeOk(); // host ACKed the mobile reply
|
|
1188
|
+
await expect(hostReply).toBeOkWith('signature'); // host received the reply
|
|
1190
1189
|
host.dispose();
|
|
1191
1190
|
mobile.dispose();
|
|
1192
1191
|
});
|
|
@@ -1212,7 +1211,7 @@ describe('session', () => {
|
|
|
1212
1211
|
mobile = makeMobile(store);
|
|
1213
1212
|
mobile.respondToRequests(RemoteMsg, () => 'success');
|
|
1214
1213
|
await settle();
|
|
1215
|
-
expect(
|
|
1214
|
+
await expect(hostAck).toBeOk();
|
|
1216
1215
|
host.dispose();
|
|
1217
1216
|
mobile.dispose();
|
|
1218
1217
|
});
|
|
@@ -1233,7 +1232,7 @@ describe('session', () => {
|
|
|
1233
1232
|
const hostAck = host.request(RemoteMsg, { id: 'h1', kind: 'request', respondingTo: '', body: 'sign this' });
|
|
1234
1233
|
await settle();
|
|
1235
1234
|
// The reply is delivered…
|
|
1236
|
-
expect(
|
|
1235
|
+
await expect(hostReply).toBeOkWith('signature');
|
|
1237
1236
|
// …while the transport ACK is still outstanding (mobile never sent it).
|
|
1238
1237
|
const ackState = await Promise.race([
|
|
1239
1238
|
Promise.resolve(hostAck).then(() => 'resolved'),
|
|
@@ -32,7 +32,7 @@ describe('statementProver', () => {
|
|
|
32
32
|
const signed = (await prover.generateMessageProof(makeStatement(new Uint8Array([1, 2, 3]))))._unsafeUnwrap();
|
|
33
33
|
expect(proofSigner(signed)).toBe(toHex(deriveSr25519PublicKey(secret)));
|
|
34
34
|
const verified = await prover.verifyMessageProof(signed);
|
|
35
|
-
expect(verified
|
|
35
|
+
await expect(verified).toBeOkWith(true);
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
38
|
describe('createSlotAccountProver (mobile slot secrets)', () => {
|
|
@@ -44,7 +44,7 @@ describe('statementProver', () => {
|
|
|
44
44
|
const signed = (await prover.generateMessageProof(makeStatement(new Uint8Array([4, 5, 6]))))._unsafeUnwrap();
|
|
45
45
|
expect(proofSigner(signed)).toBe(toHex(deriveSlotAccountPublicKey(slotSecret)));
|
|
46
46
|
const verified = await prover.verifyMessageProof(signed);
|
|
47
|
-
expect(verified
|
|
47
|
+
await expect(verified).toBeOkWith(true);
|
|
48
48
|
});
|
|
49
49
|
it('signs under a different public key than the scure prover would for the same secret', () => {
|
|
50
50
|
// Regression guard: a slot secret pushed through the scure scheme derives the WRONG
|
|
@@ -58,14 +58,14 @@ describe('statementProver', () => {
|
|
|
58
58
|
const signed = (await prover.generateMessageProof(makeStatement(new Uint8Array([7, 8, 9]))))._unsafeUnwrap();
|
|
59
59
|
const tampered = { ...signed, data: new Uint8Array([9, 9, 9]) };
|
|
60
60
|
const verified = await prover.verifyMessageProof(tampered);
|
|
61
|
-
expect(verified
|
|
61
|
+
await expect(verified).toBeOkWith(false);
|
|
62
62
|
});
|
|
63
63
|
});
|
|
64
64
|
describe('verifyMessageProof', () => {
|
|
65
65
|
it('errors when the statement carries no proof', async () => {
|
|
66
66
|
const prover = createSr25519Prover(createSr25519Secret(mnemonicToEntropy(DEV_MNEMONIC)));
|
|
67
67
|
const verified = await prover.verifyMessageProof(makeStatement(new Uint8Array([1])));
|
|
68
|
-
expect(verified
|
|
68
|
+
await expect(verified).toBeErr();
|
|
69
69
|
});
|
|
70
70
|
});
|
|
71
71
|
});
|
|
@@ -15,7 +15,7 @@ describe('submitWithRetry', () => {
|
|
|
15
15
|
let calls = 0;
|
|
16
16
|
const submit = vi.fn(() => ++calls <= 6 ? errAsync(new AccountFullError(0n, 1n)) : okAsync(undefined));
|
|
17
17
|
const result = await submitWithRetry(submit, { ...FAST, attempts: 3, priorityAttempts: 'unbounded' });
|
|
18
|
-
expect(result
|
|
18
|
+
await expect(result).toBeOk();
|
|
19
19
|
expect(calls).toBe(7);
|
|
20
20
|
});
|
|
21
21
|
it("priorityAttempts 'unbounded': a no-longer-live priority rejection settles as success", async () => {
|
|
@@ -26,13 +26,13 @@ describe('submitWithRetry', () => {
|
|
|
26
26
|
priorityAttempts: 'unbounded',
|
|
27
27
|
shouldRetry: () => false,
|
|
28
28
|
});
|
|
29
|
-
expect(result
|
|
29
|
+
await expect(result).toBeOk(); // lost the channel race — benign
|
|
30
30
|
expect(submit).toHaveBeenCalledTimes(1);
|
|
31
31
|
});
|
|
32
32
|
it('priorityAttempts budgeted: priority errors consume their budget then propagate', async () => {
|
|
33
33
|
const submit = vi.fn(() => errAsync(new AccountFullError(0n, 1n)));
|
|
34
34
|
const result = await submitWithRetry(submit, { ...FAST, attempts: 0, priorityAttempts: 3 });
|
|
35
|
-
expect(result
|
|
35
|
+
await expect(result).toBeErr();
|
|
36
36
|
expect(result._unsafeUnwrapErr()).toBeInstanceOf(AccountFullError);
|
|
37
37
|
expect(submit).toHaveBeenCalledTimes(4); // 1 initial + 3 retries
|
|
38
38
|
});
|
|
@@ -46,7 +46,7 @@ describe('submitWithRetry', () => {
|
|
|
46
46
|
priorityAttempts: 2,
|
|
47
47
|
onPriorityError: error => seen.push(error.min),
|
|
48
48
|
});
|
|
49
|
-
expect(result
|
|
49
|
+
await expect(result).toBeErr();
|
|
50
50
|
expect(submit).toHaveBeenCalledTimes(3); // 1 initial + 2 retries
|
|
51
51
|
expect(seen).toEqual([1n, 2n, 3n]); // adopted the floor on all three, including the terminal rejection
|
|
52
52
|
});
|
|
@@ -59,13 +59,13 @@ describe('submitWithRetry', () => {
|
|
|
59
59
|
it('attempts 0: a non-priority error propagates immediately', async () => {
|
|
60
60
|
const submit = vi.fn(() => errAsync(new Error('store rejected')));
|
|
61
61
|
const result = await submitWithRetry(submit, { ...FAST, attempts: 0, priorityAttempts: 3 });
|
|
62
|
-
expect(result
|
|
62
|
+
await expect(result).toBeErr();
|
|
63
63
|
expect(submit).toHaveBeenCalledTimes(1);
|
|
64
64
|
});
|
|
65
65
|
it('non-priority errors consume the attempts budget then propagate', async () => {
|
|
66
66
|
const submit = vi.fn(() => errAsync(new Error('store rejected')));
|
|
67
67
|
const result = await submitWithRetry(submit, { ...FAST, attempts: 2, priorityAttempts: 'unbounded' });
|
|
68
|
-
expect(result
|
|
68
|
+
await expect(result).toBeErr();
|
|
69
69
|
expect(submit).toHaveBeenCalledTimes(3); // 1 initial + 2 retries
|
|
70
70
|
});
|
|
71
71
|
it('per-attempt delay schedule is honored and reported via onRetry', async () => {
|
|
@@ -78,7 +78,7 @@ describe('submitWithRetry', () => {
|
|
|
78
78
|
delaysMs: [1, 2, 3],
|
|
79
79
|
onRetry: ({ attempt, delayMs }) => seen.push({ attempt, delayMs }),
|
|
80
80
|
});
|
|
81
|
-
expect(result
|
|
81
|
+
await expect(result).toBeOk();
|
|
82
82
|
expect(seen).toEqual([
|
|
83
83
|
{ attempt: 0, delayMs: 1 },
|
|
84
84
|
{ attempt: 1, delayMs: 2 },
|
|
@@ -87,7 +87,7 @@ describe('submitWithRetry', () => {
|
|
|
87
87
|
it('a negative budget propagates immediately instead of looping', async () => {
|
|
88
88
|
const submit = vi.fn(() => errAsync(new Error('store rejected')));
|
|
89
89
|
const result = await submitWithRetry(submit, { ...FAST, attempts: -1, priorityAttempts: 3 });
|
|
90
|
-
expect(result
|
|
90
|
+
await expect(result).toBeErr();
|
|
91
91
|
expect(submit).toHaveBeenCalledTimes(1);
|
|
92
92
|
});
|
|
93
93
|
it('a shouldRetry flip during the backoff settles a priority rejection as success', async () => {
|
|
@@ -104,7 +104,7 @@ describe('submitWithRetry', () => {
|
|
|
104
104
|
priorityAttempts: 'unbounded',
|
|
105
105
|
shouldRetry: () => live,
|
|
106
106
|
});
|
|
107
|
-
expect(result
|
|
107
|
+
await expect(result).toBeOk(); // settled after the delay, no second attempt
|
|
108
108
|
expect(submit).toHaveBeenCalledTimes(1);
|
|
109
109
|
});
|
|
110
110
|
it('shouldRetry is re-checked before each retry and stops the loop', async () => {
|
|
@@ -119,7 +119,7 @@ describe('submitWithRetry', () => {
|
|
|
119
119
|
priorityAttempts: 'unbounded',
|
|
120
120
|
shouldRetry: () => live,
|
|
121
121
|
});
|
|
122
|
-
expect(result
|
|
122
|
+
await expect(result).toBeErr(); // non-priority + not live → propagate, no settle
|
|
123
123
|
expect(submit).toHaveBeenCalledTimes(1);
|
|
124
124
|
});
|
|
125
125
|
});
|
|
@@ -41,7 +41,7 @@ describe('submitStatementOnce', () => {
|
|
|
41
41
|
it('submits with the pinned-high expiry layout', async () => {
|
|
42
42
|
const { adapter, submitted } = makeStore();
|
|
43
43
|
const result = await submitStatementOnce(baseParams(adapter));
|
|
44
|
-
expect(result
|
|
44
|
+
await expect(result).toBeOk();
|
|
45
45
|
expect(submitted).toHaveLength(1);
|
|
46
46
|
expect((submitted[0].expiry ?? 0n) >> 32n).toBe(0xffffffffn);
|
|
47
47
|
});
|
|
@@ -51,8 +51,8 @@ describe('submitStatementOnce', () => {
|
|
|
51
51
|
const params = baseParams(adapter);
|
|
52
52
|
const first = await submitStatementOnce(params);
|
|
53
53
|
const second = await submitStatementOnce(params);
|
|
54
|
-
expect(first
|
|
55
|
-
expect(second
|
|
54
|
+
await expect(first).toBeErr();
|
|
55
|
+
await expect(second).toBeOk();
|
|
56
56
|
expect(submitted[1].expiry ?? 0n).toBeGreaterThan(chainMin); // adopted min, not wall clock
|
|
57
57
|
});
|
|
58
58
|
});
|
|
@@ -73,7 +73,7 @@ describe('signAndSubmitStatement', () => {
|
|
|
73
73
|
});
|
|
74
74
|
await vi.advanceTimersByTimeAsync(600); // cover the 500ms first backoff
|
|
75
75
|
const result = await promise;
|
|
76
|
-
expect(result
|
|
76
|
+
await expect(result).toBeOk();
|
|
77
77
|
expect(submitted).toHaveLength(2);
|
|
78
78
|
expect(submitted[1].expiry ?? 0n).toBeGreaterThan(chainMin);
|
|
79
79
|
});
|
|
@@ -92,7 +92,7 @@ describe('signAndSubmitStatement', () => {
|
|
|
92
92
|
});
|
|
93
93
|
await vi.advanceTimersByTimeAsync(50);
|
|
94
94
|
const result = await promise;
|
|
95
|
-
expect(result
|
|
95
|
+
await expect(result).toBeErr();
|
|
96
96
|
expect(result._unsafeUnwrapErr()).toBeInstanceOf(AccountFullError);
|
|
97
97
|
expect(submitted).toHaveLength(4); // 1 initial + 3 priority retries
|
|
98
98
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novasamatech/statement-store",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.12",
|
|
5
5
|
"description": "Statement store integration",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -25,18 +25,18 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@novasamatech/scale": "0.8.
|
|
28
|
+
"@novasamatech/scale": "0.8.12",
|
|
29
29
|
"@novasamatech/sdk-statement": "^0.6.0",
|
|
30
|
-
"@novasamatech/substrate-slot-sr25519-wasm": "0.8.
|
|
30
|
+
"@novasamatech/substrate-slot-sr25519-wasm": "0.8.12",
|
|
31
31
|
"@polkadot-api/substrate-bindings": "^0.20.3",
|
|
32
32
|
"@polkadot-api/substrate-client": "^0.7.0",
|
|
33
|
-
"@polkadot-labs/hdkd-helpers": "^0.0.
|
|
33
|
+
"@polkadot-labs/hdkd-helpers": "^0.0.31",
|
|
34
34
|
"@polkadot-labs/schnorrkel-wasm": "0.0.9",
|
|
35
35
|
"@noble/hashes": "2.2.0",
|
|
36
36
|
"@noble/ciphers": "2.2.0",
|
|
37
37
|
"@scure/sr25519": "2.2.0",
|
|
38
38
|
"polkadot-api": ">=2",
|
|
39
|
-
"nanoid": "
|
|
39
|
+
"nanoid": "6.0.0",
|
|
40
40
|
"neverthrow": "^8.2.0",
|
|
41
41
|
"scale-ts": "1.6.1"
|
|
42
42
|
},
|