@mcp-i/core 0.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/LICENSE +21 -0
- package/README.md +390 -0
- package/dist/auth/handshake.d.ts +104 -0
- package/dist/auth/handshake.d.ts.map +1 -0
- package/dist/auth/handshake.js +230 -0
- package/dist/auth/handshake.js.map +1 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/types.d.ts +31 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +7 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/delegation/audience-validator.d.ts +9 -0
- package/dist/delegation/audience-validator.d.ts.map +1 -0
- package/dist/delegation/audience-validator.js +17 -0
- package/dist/delegation/audience-validator.js.map +1 -0
- package/dist/delegation/bitstring.d.ts +37 -0
- package/dist/delegation/bitstring.d.ts.map +1 -0
- package/dist/delegation/bitstring.js +117 -0
- package/dist/delegation/bitstring.js.map +1 -0
- package/dist/delegation/cascading-revocation.d.ts +45 -0
- package/dist/delegation/cascading-revocation.d.ts.map +1 -0
- package/dist/delegation/cascading-revocation.js +148 -0
- package/dist/delegation/cascading-revocation.js.map +1 -0
- package/dist/delegation/delegation-graph.d.ts +49 -0
- package/dist/delegation/delegation-graph.d.ts.map +1 -0
- package/dist/delegation/delegation-graph.js +99 -0
- package/dist/delegation/delegation-graph.js.map +1 -0
- package/dist/delegation/did-key-resolver.d.ts +64 -0
- package/dist/delegation/did-key-resolver.d.ts.map +1 -0
- package/dist/delegation/did-key-resolver.js +154 -0
- package/dist/delegation/did-key-resolver.js.map +1 -0
- package/dist/delegation/did-web-resolver.d.ts +83 -0
- package/dist/delegation/did-web-resolver.d.ts.map +1 -0
- package/dist/delegation/did-web-resolver.js +218 -0
- package/dist/delegation/did-web-resolver.js.map +1 -0
- package/dist/delegation/index.d.ts +21 -0
- package/dist/delegation/index.d.ts.map +1 -0
- package/dist/delegation/index.js +21 -0
- package/dist/delegation/index.js.map +1 -0
- package/dist/delegation/outbound-headers.d.ts +81 -0
- package/dist/delegation/outbound-headers.d.ts.map +1 -0
- package/dist/delegation/outbound-headers.js +139 -0
- package/dist/delegation/outbound-headers.js.map +1 -0
- package/dist/delegation/outbound-proof.d.ts +43 -0
- package/dist/delegation/outbound-proof.d.ts.map +1 -0
- package/dist/delegation/outbound-proof.js +52 -0
- package/dist/delegation/outbound-proof.js.map +1 -0
- package/dist/delegation/statuslist-manager.d.ts +44 -0
- package/dist/delegation/statuslist-manager.d.ts.map +1 -0
- package/dist/delegation/statuslist-manager.js +126 -0
- package/dist/delegation/statuslist-manager.js.map +1 -0
- package/dist/delegation/storage/memory-graph-storage.d.ts +70 -0
- package/dist/delegation/storage/memory-graph-storage.d.ts.map +1 -0
- package/dist/delegation/storage/memory-graph-storage.js +145 -0
- package/dist/delegation/storage/memory-graph-storage.js.map +1 -0
- package/dist/delegation/storage/memory-statuslist-storage.d.ts +19 -0
- package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +1 -0
- package/dist/delegation/storage/memory-statuslist-storage.js +33 -0
- package/dist/delegation/storage/memory-statuslist-storage.js.map +1 -0
- package/dist/delegation/utils.d.ts +49 -0
- package/dist/delegation/utils.d.ts.map +1 -0
- package/dist/delegation/utils.js +131 -0
- package/dist/delegation/utils.js.map +1 -0
- package/dist/delegation/vc-issuer.d.ts +56 -0
- package/dist/delegation/vc-issuer.d.ts.map +1 -0
- package/dist/delegation/vc-issuer.js +80 -0
- package/dist/delegation/vc-issuer.js.map +1 -0
- package/dist/delegation/vc-verifier.d.ts +112 -0
- package/dist/delegation/vc-verifier.d.ts.map +1 -0
- package/dist/delegation/vc-verifier.js +280 -0
- package/dist/delegation/vc-verifier.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/index.d.ts +2 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +2 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +23 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +82 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/middleware/index.d.ts +7 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +7 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/with-mcpi.d.ts +152 -0
- package/dist/middleware/with-mcpi.d.ts.map +1 -0
- package/dist/middleware/with-mcpi.js +472 -0
- package/dist/middleware/with-mcpi.js.map +1 -0
- package/dist/proof/errors.d.ts +49 -0
- package/dist/proof/errors.d.ts.map +1 -0
- package/dist/proof/errors.js +61 -0
- package/dist/proof/errors.js.map +1 -0
- package/dist/proof/generator.d.ts +65 -0
- package/dist/proof/generator.d.ts.map +1 -0
- package/dist/proof/generator.js +163 -0
- package/dist/proof/generator.js.map +1 -0
- package/dist/proof/index.d.ts +4 -0
- package/dist/proof/index.d.ts.map +1 -0
- package/dist/proof/index.js +4 -0
- package/dist/proof/index.js.map +1 -0
- package/dist/proof/verifier.d.ts +108 -0
- package/dist/proof/verifier.d.ts.map +1 -0
- package/dist/proof/verifier.js +299 -0
- package/dist/proof/verifier.js.map +1 -0
- package/dist/providers/base.d.ts +64 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +19 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +3 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/memory.d.ts +33 -0
- package/dist/providers/memory.d.ts.map +1 -0
- package/dist/providers/memory.js +102 -0
- package/dist/providers/memory.js.map +1 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +2 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/manager.d.ts +77 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +251 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/types/protocol.d.ts +320 -0
- package/dist/types/protocol.d.ts.map +1 -0
- package/dist/types/protocol.js +229 -0
- package/dist/types/protocol.js.map +1 -0
- package/dist/utils/base58.d.ts +31 -0
- package/dist/utils/base58.d.ts.map +1 -0
- package/dist/utils/base58.js +104 -0
- package/dist/utils/base58.js.map +1 -0
- package/dist/utils/base64.d.ts +13 -0
- package/dist/utils/base64.d.ts.map +1 -0
- package/dist/utils/base64.js +99 -0
- package/dist/utils/base64.js.map +1 -0
- package/dist/utils/crypto-service.d.ts +37 -0
- package/dist/utils/crypto-service.d.ts.map +1 -0
- package/dist/utils/crypto-service.js +153 -0
- package/dist/utils/crypto-service.js.map +1 -0
- package/dist/utils/did-helpers.d.ts +156 -0
- package/dist/utils/did-helpers.d.ts.map +1 -0
- package/dist/utils/did-helpers.js +193 -0
- package/dist/utils/did-helpers.js.map +1 -0
- package/dist/utils/ed25519-constants.d.ts +18 -0
- package/dist/utils/ed25519-constants.d.ts.map +1 -0
- package/dist/utils/ed25519-constants.js +21 -0
- package/dist/utils/ed25519-constants.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +105 -0
- package/src/__tests__/integration/full-flow.test.ts +362 -0
- package/src/__tests__/providers/base.test.ts +173 -0
- package/src/__tests__/providers/memory.test.ts +332 -0
- package/src/__tests__/utils/mock-providers.ts +319 -0
- package/src/__tests__/utils/node-crypto-provider.ts +93 -0
- package/src/auth/handshake.ts +411 -0
- package/src/auth/index.ts +11 -0
- package/src/auth/types.ts +40 -0
- package/src/delegation/__tests__/audience-validator.test.ts +110 -0
- package/src/delegation/__tests__/bitstring.test.ts +346 -0
- package/src/delegation/__tests__/cascading-revocation.test.ts +624 -0
- package/src/delegation/__tests__/delegation-graph.test.ts +623 -0
- package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
- package/src/delegation/__tests__/did-web-resolver.test.ts +467 -0
- package/src/delegation/__tests__/outbound-headers.test.ts +230 -0
- package/src/delegation/__tests__/outbound-proof.test.ts +179 -0
- package/src/delegation/__tests__/statuslist-manager.test.ts +515 -0
- package/src/delegation/__tests__/utils.test.ts +185 -0
- package/src/delegation/__tests__/vc-issuer.test.ts +487 -0
- package/src/delegation/__tests__/vc-verifier.test.ts +1029 -0
- package/src/delegation/audience-validator.ts +24 -0
- package/src/delegation/bitstring.ts +160 -0
- package/src/delegation/cascading-revocation.ts +224 -0
- package/src/delegation/delegation-graph.ts +143 -0
- package/src/delegation/did-key-resolver.ts +181 -0
- package/src/delegation/did-web-resolver.ts +270 -0
- package/src/delegation/index.ts +33 -0
- package/src/delegation/outbound-headers.ts +193 -0
- package/src/delegation/outbound-proof.ts +90 -0
- package/src/delegation/statuslist-manager.ts +219 -0
- package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
- package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
- package/src/delegation/storage/memory-graph-storage.ts +178 -0
- package/src/delegation/storage/memory-statuslist-storage.ts +42 -0
- package/src/delegation/utils.ts +189 -0
- package/src/delegation/vc-issuer.ts +137 -0
- package/src/delegation/vc-verifier.ts +440 -0
- package/src/index.ts +264 -0
- package/src/logging/__tests__/logger.test.ts +366 -0
- package/src/logging/index.ts +6 -0
- package/src/logging/logger.ts +91 -0
- package/src/middleware/__tests__/with-mcpi.test.ts +504 -0
- package/src/middleware/index.ts +16 -0
- package/src/middleware/with-mcpi.ts +766 -0
- package/src/proof/__tests__/proof-generator.test.ts +483 -0
- package/src/proof/__tests__/verifier.test.ts +488 -0
- package/src/proof/errors.ts +75 -0
- package/src/proof/generator.ts +255 -0
- package/src/proof/index.ts +22 -0
- package/src/proof/verifier.ts +449 -0
- package/src/providers/base.ts +68 -0
- package/src/providers/index.ts +15 -0
- package/src/providers/memory.ts +130 -0
- package/src/session/__tests__/session-manager.test.ts +342 -0
- package/src/session/index.ts +7 -0
- package/src/session/manager.ts +332 -0
- package/src/types/protocol.ts +596 -0
- package/src/utils/__tests__/base58.test.ts +281 -0
- package/src/utils/__tests__/base64.test.ts +239 -0
- package/src/utils/__tests__/crypto-service.test.ts +530 -0
- package/src/utils/__tests__/did-helpers.test.ts +156 -0
- package/src/utils/base58.ts +115 -0
- package/src/utils/base64.ts +116 -0
- package/src/utils/crypto-service.ts +209 -0
- package/src/utils/did-helpers.ts +210 -0
- package/src/utils/ed25519-constants.ts +23 -0
- package/src/utils/index.ts +9 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Memory Provider Implementations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
MemoryStorageProvider,
|
|
8
|
+
MemoryNonceCacheProvider,
|
|
9
|
+
MemoryIdentityProvider
|
|
10
|
+
} from '../../providers/memory.js';
|
|
11
|
+
import { MockCryptoProvider } from '../utils/mock-providers.js';
|
|
12
|
+
|
|
13
|
+
describe('MemoryStorageProvider', () => {
|
|
14
|
+
let provider: MemoryStorageProvider;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
provider = new MemoryStorageProvider();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('get', () => {
|
|
21
|
+
it('should return null for non-existent keys', async () => {
|
|
22
|
+
const value = await provider.get('nonexistent');
|
|
23
|
+
expect(value).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should return stored values', async () => {
|
|
27
|
+
await provider.set('key1', 'value1');
|
|
28
|
+
const value = await provider.get('key1');
|
|
29
|
+
expect(value).toBe('value1');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('set', () => {
|
|
34
|
+
it('should store values', async () => {
|
|
35
|
+
await provider.set('key1', 'value1');
|
|
36
|
+
const value = await provider.get('key1');
|
|
37
|
+
expect(value).toBe('value1');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should overwrite existing values', async () => {
|
|
41
|
+
await provider.set('key1', 'value1');
|
|
42
|
+
await provider.set('key1', 'value2');
|
|
43
|
+
const value = await provider.get('key1');
|
|
44
|
+
expect(value).toBe('value2');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should handle empty string values', async () => {
|
|
48
|
+
await provider.set('key1', '');
|
|
49
|
+
const value = await provider.get('key1');
|
|
50
|
+
expect(value).toBe('');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('delete', () => {
|
|
55
|
+
it('should delete existing keys', async () => {
|
|
56
|
+
await provider.set('key1', 'value1');
|
|
57
|
+
await provider.delete('key1');
|
|
58
|
+
const value = await provider.get('key1');
|
|
59
|
+
expect(value).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle deleting non-existent keys', async () => {
|
|
63
|
+
await expect(provider.delete('nonexistent')).resolves.toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('exists', () => {
|
|
68
|
+
it('should return false for non-existent keys', async () => {
|
|
69
|
+
const exists = await provider.exists('nonexistent');
|
|
70
|
+
expect(exists).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should return true for existing keys', async () => {
|
|
74
|
+
await provider.set('key1', 'value1');
|
|
75
|
+
const exists = await provider.exists('key1');
|
|
76
|
+
expect(exists).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return true for empty string values', async () => {
|
|
80
|
+
await provider.set('key1', '');
|
|
81
|
+
const exists = await provider.exists('key1');
|
|
82
|
+
expect(exists).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('list', () => {
|
|
87
|
+
beforeEach(async () => {
|
|
88
|
+
await provider.set('prefix/key1', 'value1');
|
|
89
|
+
await provider.set('prefix/key2', 'value2');
|
|
90
|
+
await provider.set('other/key3', 'value3');
|
|
91
|
+
await provider.set('key4', 'value4');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should list all keys when no prefix provided', async () => {
|
|
95
|
+
const keys = await provider.list();
|
|
96
|
+
expect(keys).toHaveLength(4);
|
|
97
|
+
expect(keys).toContain('prefix/key1');
|
|
98
|
+
expect(keys).toContain('prefix/key2');
|
|
99
|
+
expect(keys).toContain('other/key3');
|
|
100
|
+
expect(keys).toContain('key4');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should filter by prefix', async () => {
|
|
104
|
+
const keys = await provider.list('prefix/');
|
|
105
|
+
expect(keys).toHaveLength(2);
|
|
106
|
+
expect(keys).toContain('prefix/key1');
|
|
107
|
+
expect(keys).toContain('prefix/key2');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should return empty array for non-matching prefix', async () => {
|
|
111
|
+
const keys = await provider.list('nonexistent/');
|
|
112
|
+
expect(keys).toHaveLength(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should handle empty prefix', async () => {
|
|
116
|
+
const keys = await provider.list('');
|
|
117
|
+
expect(keys).toHaveLength(4);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('MemoryNonceCacheProvider', () => {
|
|
123
|
+
let provider: MemoryNonceCacheProvider;
|
|
124
|
+
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
provider = new MemoryNonceCacheProvider();
|
|
127
|
+
vi.useFakeTimers();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
afterEach(() => {
|
|
131
|
+
vi.useRealTimers();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('has', () => {
|
|
135
|
+
it('should return false for non-existent nonces', async () => {
|
|
136
|
+
const has = await provider.has('nonexistent');
|
|
137
|
+
expect(has).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should return true for valid nonces', async () => {
|
|
141
|
+
await provider.add('nonce1', 5); // 5 seconds TTL
|
|
142
|
+
const has = await provider.has('nonce1');
|
|
143
|
+
expect(has).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should return false for expired nonces', async () => {
|
|
147
|
+
await provider.add('nonce1', -1); // Negative TTL = expired
|
|
148
|
+
const has = await provider.has('nonce1');
|
|
149
|
+
expect(has).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should remove expired nonces when checking', async () => {
|
|
153
|
+
await provider.add('nonce1', -1); // Negative TTL = expired
|
|
154
|
+
await provider.has('nonce1'); // This should remove it
|
|
155
|
+
|
|
156
|
+
// Check internal state
|
|
157
|
+
const has = await provider.has('nonce1');
|
|
158
|
+
expect(has).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('add', () => {
|
|
163
|
+
it('should add nonces with expiry', async () => {
|
|
164
|
+
await provider.add('nonce1', 5); // 5 seconds TTL
|
|
165
|
+
const has = await provider.has('nonce1');
|
|
166
|
+
expect(has).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should overwrite existing nonces', async () => {
|
|
170
|
+
await provider.add('nonce1', 5); // 5 seconds TTL
|
|
171
|
+
await provider.add('nonce1', 10); // 10 seconds TTL
|
|
172
|
+
|
|
173
|
+
// Advance time by 7 seconds (between 5 and 10)
|
|
174
|
+
vi.advanceTimersByTime(7000);
|
|
175
|
+
|
|
176
|
+
const has = await provider.has('nonce1');
|
|
177
|
+
expect(has).toBe(true); // Should still be valid with new expiry
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('cleanup', () => {
|
|
182
|
+
it('should remove expired nonces', async () => {
|
|
183
|
+
await provider.add('expired', -1); // Negative TTL = expired
|
|
184
|
+
await provider.add('valid', 5); // 5 seconds TTL
|
|
185
|
+
|
|
186
|
+
await provider.cleanup();
|
|
187
|
+
|
|
188
|
+
expect(await provider.has('expired')).toBe(false);
|
|
189
|
+
expect(await provider.has('valid')).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should handle empty cache', async () => {
|
|
193
|
+
await expect(provider.cleanup()).resolves.toBeUndefined();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('destroy', () => {
|
|
198
|
+
it('should clear all nonces', async () => {
|
|
199
|
+
await provider.add('nonce1', 5); // 5 seconds TTL
|
|
200
|
+
await provider.add('nonce2', 5); // 5 seconds TTL
|
|
201
|
+
|
|
202
|
+
await provider.destroy();
|
|
203
|
+
|
|
204
|
+
expect(await provider.has('nonce1')).toBe(false);
|
|
205
|
+
expect(await provider.has('nonce2')).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('MemoryIdentityProvider', () => {
|
|
211
|
+
let provider: MemoryIdentityProvider;
|
|
212
|
+
let cryptoProvider: MockCryptoProvider;
|
|
213
|
+
|
|
214
|
+
beforeEach(() => {
|
|
215
|
+
cryptoProvider = new MockCryptoProvider();
|
|
216
|
+
provider = new MemoryIdentityProvider(cryptoProvider);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('getIdentity', () => {
|
|
220
|
+
it('should generate identity on first call', async () => {
|
|
221
|
+
const identity = await provider.getIdentity();
|
|
222
|
+
|
|
223
|
+
expect(identity).toBeDefined();
|
|
224
|
+
expect(identity.did).toMatch(/^did:key:z/);
|
|
225
|
+
expect(identity.kid).toBe(`${identity.did}#keys-1`);
|
|
226
|
+
expect(identity.privateKey).toMatch(/^[A-Za-z0-9+/=]+$/); // Valid base64 string
|
|
227
|
+
expect(identity.publicKey).toMatch(/^[A-Za-z0-9+/=]+$/); // Valid base64 string
|
|
228
|
+
expect(identity.type).toBe('development');
|
|
229
|
+
expect(identity.createdAt).toBeDefined();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should return same identity on subsequent calls', async () => {
|
|
233
|
+
const identity1 = await provider.getIdentity();
|
|
234
|
+
const identity2 = await provider.getIdentity();
|
|
235
|
+
|
|
236
|
+
expect(identity1).toEqual(identity2);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should throw without crypto provider', async () => {
|
|
240
|
+
const providerNoCrypto = new MemoryIdentityProvider();
|
|
241
|
+
await expect(providerNoCrypto.getIdentity()).rejects.toThrow('Crypto provider required');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('saveIdentity', () => {
|
|
246
|
+
it('should save and return the saved identity', async () => {
|
|
247
|
+
const customIdentity = {
|
|
248
|
+
did: 'did:key:zcustom',
|
|
249
|
+
kid: 'did:key:zcustom#keys-1',
|
|
250
|
+
privateKey: 'custom-private',
|
|
251
|
+
publicKey: 'custom-public',
|
|
252
|
+
createdAt: new Date().toISOString(),
|
|
253
|
+
type: 'production' as const,
|
|
254
|
+
metadata: { custom: true }
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
await provider.saveIdentity(customIdentity);
|
|
258
|
+
const retrieved = await provider.getIdentity();
|
|
259
|
+
|
|
260
|
+
expect(retrieved).toEqual(customIdentity);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('rotateKeys', () => {
|
|
265
|
+
it('should generate new identity', async () => {
|
|
266
|
+
const oldIdentity = await provider.getIdentity();
|
|
267
|
+
// Change the mock to return different keys for rotation
|
|
268
|
+
cryptoProvider.keyPairResult = {
|
|
269
|
+
privateKey: 'cm90YXRlZC1wcml2YXRlLWtleQ==', // base64 of "rotated-private-key"
|
|
270
|
+
publicKey: 'cm90YXRlZC1wdWJsaWMta2V5', // base64 of "rotated-public-key"
|
|
271
|
+
};
|
|
272
|
+
const newIdentity = await provider.rotateKeys();
|
|
273
|
+
|
|
274
|
+
expect(newIdentity.did).not.toBe(oldIdentity.did);
|
|
275
|
+
expect(newIdentity.privateKey).toMatch(/^[A-Za-z0-9+/=]+$/); // Valid base64 string
|
|
276
|
+
expect(newIdentity.publicKey).toMatch(/^[A-Za-z0-9+/=]+$/); // Valid base64 string
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should return new identity on subsequent calls', async () => {
|
|
280
|
+
const oldIdentity = await provider.getIdentity();
|
|
281
|
+
// Change the mock to return different keys for rotation
|
|
282
|
+
cryptoProvider.keyPairResult = {
|
|
283
|
+
privateKey: 'cm90YXRlZC1wcml2YXRlLWtleQ==',
|
|
284
|
+
publicKey: 'cm90YXRlZC1wdWJsaWMta2V5',
|
|
285
|
+
};
|
|
286
|
+
await provider.rotateKeys();
|
|
287
|
+
const currentIdentity = await provider.getIdentity();
|
|
288
|
+
|
|
289
|
+
expect(currentIdentity.did).not.toBe(oldIdentity.did);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should throw without crypto provider', async () => {
|
|
293
|
+
const providerNoCrypto = new MemoryIdentityProvider();
|
|
294
|
+
await expect(providerNoCrypto.rotateKeys()).rejects.toThrow('Crypto provider required');
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('deleteIdentity', () => {
|
|
299
|
+
it('should delete existing identity', async () => {
|
|
300
|
+
const identity = await provider.getIdentity();
|
|
301
|
+
// Change mock keys so regenerated identity differs
|
|
302
|
+
cryptoProvider.keyPairResult = {
|
|
303
|
+
privateKey: 'ZGVsZXRlZC1wcml2YXRlLWtleQ==', // base64 of "deleted-private-key"
|
|
304
|
+
publicKey: 'ZGVsZXRlZC1wdWJsaWMta2V5', // base64 of "deleted-public-key"
|
|
305
|
+
};
|
|
306
|
+
await provider.deleteIdentity();
|
|
307
|
+
|
|
308
|
+
// Should generate new identity after deletion
|
|
309
|
+
const newIdentity = await provider.getIdentity();
|
|
310
|
+
expect(newIdentity.did).not.toBe(identity.did);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should handle deleting when no identity exists', async () => {
|
|
314
|
+
await expect(provider.deleteIdentity()).resolves.toBeUndefined();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('DID generation', () => {
|
|
319
|
+
it('should generate consistent DID format', async () => {
|
|
320
|
+
const identity = await provider.getIdentity();
|
|
321
|
+
expect(identity.did).toMatch(/^did:key:z[A-Za-z0-9]+$/);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('should generate proper did:key DID from public key', async () => {
|
|
325
|
+
const identity = await provider.getIdentity();
|
|
326
|
+
// The DID should start with did:key:z (multibase base58btc prefix)
|
|
327
|
+
expect(identity.did).toMatch(/^did:key:z/);
|
|
328
|
+
// DID should be longer than just the prefix (base58btc encoded Ed25519 multicodec + key)
|
|
329
|
+
expect(identity.did.length).toBeGreaterThan(10);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
});
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Provider Implementations for Testing
|
|
3
|
+
*
|
|
4
|
+
* Provides controllable mock versions of all provider abstract classes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { vi } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
CryptoProvider,
|
|
10
|
+
ClockProvider,
|
|
11
|
+
FetchProvider,
|
|
12
|
+
StorageProvider,
|
|
13
|
+
NonceCacheProvider,
|
|
14
|
+
IdentityProvider,
|
|
15
|
+
type AgentIdentity,
|
|
16
|
+
} from '../../providers/base.js';
|
|
17
|
+
import type { DIDDocument } from '../../delegation/vc-verifier.js';
|
|
18
|
+
import type { StatusList2021Credential, DelegationRecord } from '../../types/protocol.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Mock Crypto Provider
|
|
22
|
+
*/
|
|
23
|
+
export class MockCryptoProvider extends CryptoProvider {
|
|
24
|
+
public signResult = new Uint8Array([1, 2, 3, 4]);
|
|
25
|
+
public verifyResult = true;
|
|
26
|
+
public hashResult = 'sha256:mock-hash';
|
|
27
|
+
public keyPairResult = {
|
|
28
|
+
privateKey: 'bW9jay1wcml2YXRlLWtleQ==', // base64 of "mock-private-key"
|
|
29
|
+
publicKey: 'bW9jay1wdWJsaWMta2V5', // base64 of "mock-public-key"
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async sign(_data: Uint8Array, _privateKey: string): Promise<Uint8Array> {
|
|
33
|
+
return this.signResult;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async verify(
|
|
37
|
+
_data: Uint8Array,
|
|
38
|
+
_signature: Uint8Array,
|
|
39
|
+
_publicKey: string
|
|
40
|
+
): Promise<boolean> {
|
|
41
|
+
return this.verifyResult;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async generateKeyPair(): Promise<{ privateKey: string; publicKey: string }> {
|
|
45
|
+
return this.keyPairResult;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async hash(_data: Uint8Array): Promise<string> {
|
|
49
|
+
return this.hashResult;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async randomBytes(length: number): Promise<Uint8Array> {
|
|
53
|
+
return new Uint8Array(length).fill(42);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Mock Clock Provider
|
|
59
|
+
*/
|
|
60
|
+
export class MockClockProvider extends ClockProvider {
|
|
61
|
+
private currentTime: number = Date.now();
|
|
62
|
+
|
|
63
|
+
setTime(time: number): void {
|
|
64
|
+
this.currentTime = time;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
advance(ms: number): void {
|
|
68
|
+
this.currentTime += ms;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
now(): number {
|
|
72
|
+
return this.currentTime;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
isWithinSkew(timestamp: number, skewSeconds: number): boolean {
|
|
76
|
+
const skewMs = skewSeconds * 1000;
|
|
77
|
+
return Math.abs(this.currentTime - timestamp) <= skewMs;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
hasExpired(expiresAt: number): boolean {
|
|
81
|
+
return this.currentTime > expiresAt;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
calculateExpiry(ttlSeconds: number): number {
|
|
85
|
+
return this.currentTime + ttlSeconds * 1000;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
format(timestamp: number): string {
|
|
89
|
+
return new Date(timestamp).toISOString();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Mock Fetch Provider
|
|
95
|
+
*/
|
|
96
|
+
export class MockFetchProvider extends FetchProvider {
|
|
97
|
+
private didDocuments = new Map<string, DIDDocument>();
|
|
98
|
+
private statusLists = new Map<string, StatusList2021Credential>();
|
|
99
|
+
private delegationChains = new Map<string, DelegationRecord[]>();
|
|
100
|
+
public fetch: (url: string, options?: unknown) => Promise<Response>;
|
|
101
|
+
|
|
102
|
+
constructor() {
|
|
103
|
+
super();
|
|
104
|
+
this.fetch = vi.fn(
|
|
105
|
+
async (url: string, _options?: unknown): Promise<Response> => {
|
|
106
|
+
return new Response(JSON.stringify({ url }), {
|
|
107
|
+
status: 200,
|
|
108
|
+
headers: { 'Content-Type': 'application/json' },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
) as (url: string, options?: unknown) => Promise<Response>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setDIDDocument(did: string, doc: DIDDocument): void {
|
|
115
|
+
this.didDocuments.set(did, doc);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setStatusList(url: string, list: StatusList2021Credential): void {
|
|
119
|
+
this.statusLists.set(url, list);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
setDelegationChain(id: string, chain: DelegationRecord[]): void {
|
|
123
|
+
this.delegationChains.set(id, chain);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async resolveDID(did: string): Promise<DIDDocument | null> {
|
|
127
|
+
return this.didDocuments.get(did) ?? null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async fetchStatusList(url: string): Promise<StatusList2021Credential | null> {
|
|
131
|
+
return this.statusLists.get(url) ?? null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async fetchDelegationChain(id: string): Promise<DelegationRecord[]> {
|
|
135
|
+
return this.delegationChains.get(id) ?? [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Mock Storage Provider
|
|
141
|
+
*/
|
|
142
|
+
export class MockStorageProvider extends StorageProvider {
|
|
143
|
+
private store: Map<string, string> = new Map();
|
|
144
|
+
|
|
145
|
+
async get(key: string): Promise<string | null> {
|
|
146
|
+
return this.store.get(key) || null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async set(key: string, value: string): Promise<void> {
|
|
150
|
+
this.store.set(key, value);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async delete(key: string): Promise<void> {
|
|
154
|
+
this.store.delete(key);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async exists(key: string): Promise<boolean> {
|
|
158
|
+
return this.store.has(key);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async list(prefix?: string): Promise<string[]> {
|
|
162
|
+
const keys = Array.from(this.store.keys());
|
|
163
|
+
if (prefix) {
|
|
164
|
+
return keys.filter((k) => k.startsWith(prefix));
|
|
165
|
+
}
|
|
166
|
+
return keys;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
clear(): void {
|
|
170
|
+
this.store.clear();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Mock Nonce Cache Provider
|
|
176
|
+
*/
|
|
177
|
+
export class MockNonceCacheProvider extends NonceCacheProvider {
|
|
178
|
+
private nonces: Map<string, number> = new Map();
|
|
179
|
+
public cleanupCalled = false;
|
|
180
|
+
public destroyCalled = false;
|
|
181
|
+
private clock?: ClockProvider;
|
|
182
|
+
|
|
183
|
+
setClock(clock: ClockProvider): void {
|
|
184
|
+
this.clock = clock;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async has(nonce: string, agentDid?: string): Promise<boolean> {
|
|
188
|
+
const key = agentDid ? `nonce:${agentDid}:${nonce}` : `nonce:${nonce}`;
|
|
189
|
+
const expiry = this.nonces.get(key);
|
|
190
|
+
if (!expiry) return false;
|
|
191
|
+
|
|
192
|
+
const now = this.clock ? this.clock.now() : Date.now();
|
|
193
|
+
if (now > expiry) {
|
|
194
|
+
this.nonces.delete(key);
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async add(
|
|
202
|
+
nonce: string,
|
|
203
|
+
ttlSeconds: number,
|
|
204
|
+
agentDid?: string
|
|
205
|
+
): Promise<void> {
|
|
206
|
+
const key = agentDid ? `nonce:${agentDid}:${nonce}` : `nonce:${nonce}`;
|
|
207
|
+
const now = this.clock ? this.clock.now() : Date.now();
|
|
208
|
+
const expiresAt = now + ttlSeconds * 1000;
|
|
209
|
+
this.nonces.set(key, expiresAt);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async cleanup(): Promise<void> {
|
|
213
|
+
this.cleanupCalled = true;
|
|
214
|
+
const now = this.clock ? this.clock.now() : Date.now();
|
|
215
|
+
for (const [nonce, expiry] of this.nonces) {
|
|
216
|
+
if (now > expiry) {
|
|
217
|
+
this.nonces.delete(nonce);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async destroy(): Promise<void> {
|
|
223
|
+
this.destroyCalled = true;
|
|
224
|
+
this.nonces.clear();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
clear(): void {
|
|
228
|
+
this.nonces.clear();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
size(): number {
|
|
232
|
+
return this.nonces.size;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Mock Identity Provider
|
|
238
|
+
*/
|
|
239
|
+
export class MockIdentityProvider extends IdentityProvider {
|
|
240
|
+
private identity?: AgentIdentity;
|
|
241
|
+
public rotateKeysCalled = false;
|
|
242
|
+
public deleteIdentityCalled = false;
|
|
243
|
+
private rotateCount = 0;
|
|
244
|
+
|
|
245
|
+
constructor(identity?: AgentIdentity) {
|
|
246
|
+
super();
|
|
247
|
+
this.identity = identity;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async getIdentity(): Promise<AgentIdentity> {
|
|
251
|
+
if (!this.identity) {
|
|
252
|
+
this.identity = {
|
|
253
|
+
did: 'did:key:zmock123',
|
|
254
|
+
kid: 'did:key:zmock123#keys-1',
|
|
255
|
+
privateKey: 'mock-private-key',
|
|
256
|
+
publicKey: 'mock-public-key',
|
|
257
|
+
createdAt: new Date().toISOString(),
|
|
258
|
+
type: 'development',
|
|
259
|
+
metadata: { mock: true },
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return this.identity;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async saveIdentity(identity: AgentIdentity): Promise<void> {
|
|
266
|
+
this.identity = identity;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async rotateKeys(): Promise<AgentIdentity> {
|
|
270
|
+
this.rotateKeysCalled = true;
|
|
271
|
+
this.rotateCount++;
|
|
272
|
+
this.identity = {
|
|
273
|
+
did: `did:key:zmock456-${this.rotateCount}`,
|
|
274
|
+
kid: `did:key:zmock456-${this.rotateCount}#keys-1`,
|
|
275
|
+
privateKey: `mock-private-key-rotated-${this.rotateCount}`,
|
|
276
|
+
publicKey: `mock-public-key-rotated-${this.rotateCount}`,
|
|
277
|
+
createdAt: new Date().toISOString(),
|
|
278
|
+
type: 'development',
|
|
279
|
+
metadata: {
|
|
280
|
+
mock: true,
|
|
281
|
+
rotated: true,
|
|
282
|
+
rotateCount: this.rotateCount,
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
return this.identity;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async deleteIdentity(): Promise<void> {
|
|
289
|
+
this.deleteIdentityCalled = true;
|
|
290
|
+
this.identity = undefined;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
setIdentity(identity: AgentIdentity): void {
|
|
294
|
+
this.identity = identity;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Create a full set of mock providers for testing
|
|
300
|
+
*/
|
|
301
|
+
export function createMockProviders() {
|
|
302
|
+
const cryptoProvider = new MockCryptoProvider();
|
|
303
|
+
const clockProvider = new MockClockProvider();
|
|
304
|
+
const fetchProvider = new MockFetchProvider();
|
|
305
|
+
const storageProvider = new MockStorageProvider();
|
|
306
|
+
const nonceCacheProvider = new MockNonceCacheProvider();
|
|
307
|
+
const identityProvider = new MockIdentityProvider();
|
|
308
|
+
|
|
309
|
+
nonceCacheProvider.setClock(clockProvider);
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
cryptoProvider,
|
|
313
|
+
clockProvider,
|
|
314
|
+
fetchProvider,
|
|
315
|
+
storageProvider,
|
|
316
|
+
nonceCacheProvider,
|
|
317
|
+
identityProvider,
|
|
318
|
+
};
|
|
319
|
+
}
|