@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,467 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
DidWebResolver,
|
|
4
|
+
createDidWebResolver,
|
|
5
|
+
isDidWeb,
|
|
6
|
+
parseDidWeb,
|
|
7
|
+
didWebToUrl,
|
|
8
|
+
} from '../did-web-resolver.js';
|
|
9
|
+
import type { FetchProvider } from '../../providers/base.js';
|
|
10
|
+
import type { DIDDocument } from '../vc-verifier.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tests for did:web resolver
|
|
14
|
+
*
|
|
15
|
+
* These tests verify the did:web resolution functionality:
|
|
16
|
+
* - URL construction for root domain DIDs
|
|
17
|
+
* - URL construction for path-based DIDs
|
|
18
|
+
* - Successful resolution with mocked fetch
|
|
19
|
+
* - Null return on 404
|
|
20
|
+
* - Null return on invalid JSON
|
|
21
|
+
* - Null return on missing `id` field in response
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
describe('did:web URL Construction', () => {
|
|
25
|
+
describe('isDidWeb', () => {
|
|
26
|
+
it('should return true for did:web DIDs', () => {
|
|
27
|
+
expect(isDidWeb('did:web:example.com')).toBe(true);
|
|
28
|
+
expect(isDidWeb('did:web:example.com:path')).toBe(true);
|
|
29
|
+
expect(isDidWeb('did:web:sub.example.com')).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return false for non-did:web DIDs', () => {
|
|
33
|
+
expect(isDidWeb('did:key:z6Mk...')).toBe(false);
|
|
34
|
+
expect(isDidWeb('did:example:123')).toBe(false);
|
|
35
|
+
expect(isDidWeb('not-a-did')).toBe(false);
|
|
36
|
+
expect(isDidWeb('')).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('parseDidWeb', () => {
|
|
41
|
+
it('should parse root domain DID', () => {
|
|
42
|
+
const result = parseDidWeb('did:web:example.com');
|
|
43
|
+
expect(result).not.toBeNull();
|
|
44
|
+
expect(result?.domain).toBe('example.com');
|
|
45
|
+
expect(result?.path).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should parse path-based DID', () => {
|
|
49
|
+
const result = parseDidWeb('did:web:example.com:path:to:doc');
|
|
50
|
+
expect(result).not.toBeNull();
|
|
51
|
+
expect(result?.domain).toBe('example.com');
|
|
52
|
+
expect(result?.path).toEqual(['path', 'to', 'doc']);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle URL-encoded components', () => {
|
|
56
|
+
// %3A is URL-encoded colon, which would be used for port numbers
|
|
57
|
+
const result = parseDidWeb('did:web:example.com%3A8080');
|
|
58
|
+
expect(result).not.toBeNull();
|
|
59
|
+
expect(result?.domain).toBe('example.com:8080');
|
|
60
|
+
expect(result?.path).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return null for invalid DIDs', () => {
|
|
64
|
+
expect(parseDidWeb('did:key:z6Mk...')).toBeNull();
|
|
65
|
+
expect(parseDidWeb('did:web:')).toBeNull();
|
|
66
|
+
expect(parseDidWeb('')).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('didWebToUrl', () => {
|
|
71
|
+
it('should convert root domain DID to .well-known URL', () => {
|
|
72
|
+
const url = didWebToUrl('did:web:example.com');
|
|
73
|
+
expect(url).toBe('https://example.com/.well-known/did.json');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should convert subdomain DID to .well-known URL', () => {
|
|
77
|
+
const url = didWebToUrl('did:web:agents.example.com');
|
|
78
|
+
expect(url).toBe('https://agents.example.com/.well-known/did.json');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should convert single path component DID to path URL', () => {
|
|
82
|
+
const url = didWebToUrl('did:web:example.com:user');
|
|
83
|
+
expect(url).toBe('https://example.com/user/did.json');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should convert multi-path DID to path URL', () => {
|
|
87
|
+
const url = didWebToUrl('did:web:example.com:path:to:doc');
|
|
88
|
+
expect(url).toBe('https://example.com/path/to/doc/did.json');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should handle agents path pattern', () => {
|
|
92
|
+
const url = didWebToUrl('did:web:example.com:agents:bot1');
|
|
93
|
+
expect(url).toBe('https://example.com/agents/bot1/did.json');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should handle URL-encoded port number', () => {
|
|
97
|
+
const url = didWebToUrl('did:web:example.com%3A8080');
|
|
98
|
+
expect(url).toBe('https://example.com:8080/.well-known/did.json');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should handle URL-encoded port number with path', () => {
|
|
102
|
+
const url = didWebToUrl('did:web:example.com%3A3000:users:alice');
|
|
103
|
+
expect(url).toBe('https://example.com:3000/users/alice/did.json');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should return null for invalid DIDs', () => {
|
|
107
|
+
expect(didWebToUrl('did:key:z6Mk...')).toBeNull();
|
|
108
|
+
expect(didWebToUrl('did:web:')).toBeNull();
|
|
109
|
+
expect(didWebToUrl('')).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('DidWebResolver', () => {
|
|
115
|
+
let mockFetchProvider: FetchProvider;
|
|
116
|
+
let resolver: DidWebResolver;
|
|
117
|
+
|
|
118
|
+
const validDIDDocument: DIDDocument = {
|
|
119
|
+
id: 'did:web:example.com',
|
|
120
|
+
verificationMethod: [
|
|
121
|
+
{
|
|
122
|
+
id: 'did:web:example.com#key-1',
|
|
123
|
+
type: 'Ed25519VerificationKey2020',
|
|
124
|
+
controller: 'did:web:example.com',
|
|
125
|
+
publicKeyJwk: {
|
|
126
|
+
kty: 'OKP',
|
|
127
|
+
crv: 'Ed25519',
|
|
128
|
+
x: 'test-public-key-base64url',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
authentication: ['did:web:example.com#key-1'],
|
|
133
|
+
assertionMethod: ['did:web:example.com#key-1'],
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const createMockFetchProvider = (
|
|
137
|
+
responseBody: unknown,
|
|
138
|
+
status = 200
|
|
139
|
+
): FetchProvider => {
|
|
140
|
+
return {
|
|
141
|
+
resolveDID: vi.fn(),
|
|
142
|
+
fetchStatusList: vi.fn(),
|
|
143
|
+
fetchDelegationChain: vi.fn(),
|
|
144
|
+
fetch: vi.fn().mockResolvedValue({
|
|
145
|
+
ok: status >= 200 && status < 300,
|
|
146
|
+
status,
|
|
147
|
+
json: vi.fn().mockResolvedValue(responseBody),
|
|
148
|
+
}),
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const createMockFetchProviderWithJsonError = (): FetchProvider => {
|
|
153
|
+
return {
|
|
154
|
+
resolveDID: vi.fn(),
|
|
155
|
+
fetchStatusList: vi.fn(),
|
|
156
|
+
fetchDelegationChain: vi.fn(),
|
|
157
|
+
fetch: vi.fn().mockResolvedValue({
|
|
158
|
+
ok: true,
|
|
159
|
+
status: 200,
|
|
160
|
+
json: vi.fn().mockRejectedValue(new Error('Invalid JSON')),
|
|
161
|
+
}),
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const createMockFetchProviderWithNetworkError = (): FetchProvider => {
|
|
166
|
+
return {
|
|
167
|
+
resolveDID: vi.fn(),
|
|
168
|
+
fetchStatusList: vi.fn(),
|
|
169
|
+
fetchDelegationChain: vi.fn(),
|
|
170
|
+
fetch: vi.fn().mockRejectedValue(new Error('Network error')),
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
beforeEach(() => {
|
|
175
|
+
mockFetchProvider = createMockFetchProvider(validDIDDocument);
|
|
176
|
+
resolver = new DidWebResolver(mockFetchProvider, { cacheTtl: 1000 });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('successful resolution', () => {
|
|
180
|
+
it('should resolve did:web:example.com', async () => {
|
|
181
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
182
|
+
|
|
183
|
+
expect(result).not.toBeNull();
|
|
184
|
+
expect(result?.id).toBe('did:web:example.com');
|
|
185
|
+
expect(result?.verificationMethod).toHaveLength(1);
|
|
186
|
+
expect(result?.verificationMethod?.[0]?.type).toBe('Ed25519VerificationKey2020');
|
|
187
|
+
expect(mockFetchProvider.fetch).toHaveBeenCalledWith(
|
|
188
|
+
'https://example.com/.well-known/did.json'
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should resolve path-based DID', async () => {
|
|
193
|
+
const pathDIDDocument: DIDDocument = {
|
|
194
|
+
...validDIDDocument,
|
|
195
|
+
id: 'did:web:example.com:agents:bot1',
|
|
196
|
+
verificationMethod: [
|
|
197
|
+
{
|
|
198
|
+
...validDIDDocument.verificationMethod![0]!,
|
|
199
|
+
id: 'did:web:example.com:agents:bot1#key-1',
|
|
200
|
+
controller: 'did:web:example.com:agents:bot1',
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
mockFetchProvider = createMockFetchProvider(pathDIDDocument);
|
|
206
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
207
|
+
|
|
208
|
+
const result = await resolver.resolve('did:web:example.com:agents:bot1');
|
|
209
|
+
|
|
210
|
+
expect(result).not.toBeNull();
|
|
211
|
+
expect(result?.id).toBe('did:web:example.com:agents:bot1');
|
|
212
|
+
expect(mockFetchProvider.fetch).toHaveBeenCalledWith(
|
|
213
|
+
'https://example.com/agents/bot1/did.json'
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should cache successful resolutions', async () => {
|
|
218
|
+
await resolver.resolve('did:web:example.com');
|
|
219
|
+
await resolver.resolve('did:web:example.com');
|
|
220
|
+
|
|
221
|
+
// Should only fetch once due to caching
|
|
222
|
+
expect(mockFetchProvider.fetch).toHaveBeenCalledTimes(1);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should return cached result', async () => {
|
|
226
|
+
const result1 = await resolver.resolve('did:web:example.com');
|
|
227
|
+
const result2 = await resolver.resolve('did:web:example.com');
|
|
228
|
+
|
|
229
|
+
expect(result1).toEqual(result2);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('null return on 404', () => {
|
|
234
|
+
it('should return null for 404 response', async () => {
|
|
235
|
+
mockFetchProvider = createMockFetchProvider({}, 404);
|
|
236
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
237
|
+
|
|
238
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
239
|
+
|
|
240
|
+
expect(result).toBeNull();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should return null for 500 response', async () => {
|
|
244
|
+
mockFetchProvider = createMockFetchProvider({}, 500);
|
|
245
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
246
|
+
|
|
247
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
248
|
+
|
|
249
|
+
expect(result).toBeNull();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('null return on invalid JSON', () => {
|
|
254
|
+
it('should return null when JSON parsing fails', async () => {
|
|
255
|
+
mockFetchProvider = createMockFetchProviderWithJsonError();
|
|
256
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
257
|
+
|
|
258
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
259
|
+
|
|
260
|
+
expect(result).toBeNull();
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('null return on missing id field', () => {
|
|
265
|
+
it('should return null when id field is missing', async () => {
|
|
266
|
+
const invalidDocument = {
|
|
267
|
+
verificationMethod: [],
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
mockFetchProvider = createMockFetchProvider(invalidDocument);
|
|
271
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
272
|
+
|
|
273
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
274
|
+
|
|
275
|
+
expect(result).toBeNull();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should return null when id field is empty string', async () => {
|
|
279
|
+
const invalidDocument = {
|
|
280
|
+
id: '',
|
|
281
|
+
verificationMethod: [],
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
mockFetchProvider = createMockFetchProvider(invalidDocument);
|
|
285
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
286
|
+
|
|
287
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
288
|
+
|
|
289
|
+
expect(result).toBeNull();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should return null when id does not match requested DID', async () => {
|
|
293
|
+
const mismatchedDocument: DIDDocument = {
|
|
294
|
+
...validDIDDocument,
|
|
295
|
+
id: 'did:web:other.com',
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
mockFetchProvider = createMockFetchProvider(mismatchedDocument);
|
|
299
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
300
|
+
|
|
301
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
302
|
+
|
|
303
|
+
expect(result).toBeNull();
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('null return on network error', () => {
|
|
308
|
+
it('should return null when fetch throws', async () => {
|
|
309
|
+
mockFetchProvider = createMockFetchProviderWithNetworkError();
|
|
310
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
311
|
+
|
|
312
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
313
|
+
|
|
314
|
+
expect(result).toBeNull();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('null return for non-did:web', () => {
|
|
319
|
+
it('should return null for did:key', async () => {
|
|
320
|
+
const result = await resolver.resolve(
|
|
321
|
+
'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK'
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect(result).toBeNull();
|
|
325
|
+
expect(mockFetchProvider.fetch).not.toHaveBeenCalled();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should return null for invalid DID format', async () => {
|
|
329
|
+
const result = await resolver.resolve('not-a-did');
|
|
330
|
+
|
|
331
|
+
expect(result).toBeNull();
|
|
332
|
+
expect(mockFetchProvider.fetch).not.toHaveBeenCalled();
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe('invalid DID document structure', () => {
|
|
337
|
+
it('should return null when response is not an object', async () => {
|
|
338
|
+
mockFetchProvider = createMockFetchProvider('not an object');
|
|
339
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
340
|
+
|
|
341
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
342
|
+
|
|
343
|
+
expect(result).toBeNull();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('should return null when response is null', async () => {
|
|
347
|
+
mockFetchProvider = createMockFetchProvider(null);
|
|
348
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
349
|
+
|
|
350
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
351
|
+
|
|
352
|
+
expect(result).toBeNull();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should return null when verificationMethod is not an array', async () => {
|
|
356
|
+
const invalidDocument = {
|
|
357
|
+
id: 'did:web:example.com',
|
|
358
|
+
verificationMethod: 'not an array',
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
mockFetchProvider = createMockFetchProvider(invalidDocument);
|
|
362
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
363
|
+
|
|
364
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
365
|
+
|
|
366
|
+
expect(result).toBeNull();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should return null when verificationMethod entry is invalid', async () => {
|
|
370
|
+
const invalidDocument = {
|
|
371
|
+
id: 'did:web:example.com',
|
|
372
|
+
verificationMethod: [
|
|
373
|
+
{
|
|
374
|
+
// Missing required id field
|
|
375
|
+
type: 'Ed25519VerificationKey2020',
|
|
376
|
+
controller: 'did:web:example.com',
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
mockFetchProvider = createMockFetchProvider(invalidDocument);
|
|
382
|
+
resolver = new DidWebResolver(mockFetchProvider);
|
|
383
|
+
|
|
384
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
385
|
+
|
|
386
|
+
expect(result).toBeNull();
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
describe('cache management', () => {
|
|
391
|
+
it('should clear all cache entries', async () => {
|
|
392
|
+
await resolver.resolve('did:web:example.com');
|
|
393
|
+
|
|
394
|
+
resolver.clearCache();
|
|
395
|
+
|
|
396
|
+
// Next call should fetch again
|
|
397
|
+
await resolver.resolve('did:web:example.com');
|
|
398
|
+
|
|
399
|
+
expect(mockFetchProvider.fetch).toHaveBeenCalledTimes(2);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('should clear specific cache entry', async () => {
|
|
403
|
+
await resolver.resolve('did:web:example.com');
|
|
404
|
+
|
|
405
|
+
resolver.clearCacheEntry('did:web:example.com');
|
|
406
|
+
|
|
407
|
+
// Next call should fetch again
|
|
408
|
+
await resolver.resolve('did:web:example.com');
|
|
409
|
+
|
|
410
|
+
expect(mockFetchProvider.fetch).toHaveBeenCalledTimes(2);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should expire cached entries after TTL', async () => {
|
|
414
|
+
// Use very short TTL
|
|
415
|
+
resolver = new DidWebResolver(mockFetchProvider, { cacheTtl: 10 });
|
|
416
|
+
|
|
417
|
+
await resolver.resolve('did:web:example.com');
|
|
418
|
+
|
|
419
|
+
// Wait for cache to expire
|
|
420
|
+
await new Promise((resolve) => setTimeout(resolve, 15));
|
|
421
|
+
|
|
422
|
+
await resolver.resolve('did:web:example.com');
|
|
423
|
+
|
|
424
|
+
expect(mockFetchProvider.fetch).toHaveBeenCalledTimes(2);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
describe('createDidWebResolver', () => {
|
|
430
|
+
it('should create a resolver instance', () => {
|
|
431
|
+
const mockFetchProvider: FetchProvider = {
|
|
432
|
+
resolveDID: vi.fn(),
|
|
433
|
+
fetchStatusList: vi.fn(),
|
|
434
|
+
fetchDelegationChain: vi.fn(),
|
|
435
|
+
fetch: vi.fn(),
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const resolver = createDidWebResolver(mockFetchProvider);
|
|
439
|
+
|
|
440
|
+
expect(resolver).toBeDefined();
|
|
441
|
+
expect(typeof resolver.resolve).toBe('function');
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('should pass options to resolver', async () => {
|
|
445
|
+
const validDIDDocument: DIDDocument = {
|
|
446
|
+
id: 'did:web:example.com',
|
|
447
|
+
verificationMethod: [],
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const mockFetchProvider: FetchProvider = {
|
|
451
|
+
resolveDID: vi.fn(),
|
|
452
|
+
fetchStatusList: vi.fn(),
|
|
453
|
+
fetchDelegationChain: vi.fn(),
|
|
454
|
+
fetch: vi.fn().mockResolvedValue({
|
|
455
|
+
ok: true,
|
|
456
|
+
status: 200,
|
|
457
|
+
json: vi.fn().mockResolvedValue(validDIDDocument),
|
|
458
|
+
}),
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const resolver = createDidWebResolver(mockFetchProvider, { cacheTtl: 5000 });
|
|
462
|
+
|
|
463
|
+
const result = await resolver.resolve('did:web:example.com');
|
|
464
|
+
|
|
465
|
+
expect(result).not.toBeNull();
|
|
466
|
+
});
|
|
467
|
+
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound Delegation Headers Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for buildOutboundDelegationHeaders utility.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
8
|
+
import { decodeJwt, decodeProtectedHeader } from 'jose';
|
|
9
|
+
import {
|
|
10
|
+
buildOutboundDelegationHeaders,
|
|
11
|
+
type OutboundDelegationContext,
|
|
12
|
+
} from '../outbound-headers.js';
|
|
13
|
+
import type { SessionContext, DelegationRecord } from '../../types/protocol.js';
|
|
14
|
+
import { NodeCryptoProvider } from '../../__tests__/utils/node-crypto-provider.js';
|
|
15
|
+
import { generateDidKeyFromBase64 } from '../../utils/did-helpers.js';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Test fixtures
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
let cryptoProvider: NodeCryptoProvider;
|
|
22
|
+
let serverKeyPair: { privateKey: string; publicKey: string };
|
|
23
|
+
let serverDid: string;
|
|
24
|
+
let serverKid: string;
|
|
25
|
+
let agentKeyPair: { privateKey: string; publicKey: string };
|
|
26
|
+
let agentDid: string;
|
|
27
|
+
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
cryptoProvider = new NodeCryptoProvider();
|
|
30
|
+
|
|
31
|
+
// Generate server identity
|
|
32
|
+
serverKeyPair = await cryptoProvider.generateKeyPair();
|
|
33
|
+
serverDid = generateDidKeyFromBase64(serverKeyPair.publicKey);
|
|
34
|
+
serverKid = `${serverDid}#keys-1`;
|
|
35
|
+
|
|
36
|
+
// Generate agent identity
|
|
37
|
+
agentKeyPair = await cryptoProvider.generateKeyPair();
|
|
38
|
+
agentDid = generateDidKeyFromBase64(agentKeyPair.publicKey);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function createTestSession(overrides: Partial<SessionContext> = {}): SessionContext {
|
|
42
|
+
return {
|
|
43
|
+
sessionId: 'mcpi_test-session-123',
|
|
44
|
+
audience: 'did:web:my-mcp-server.example.com',
|
|
45
|
+
nonce: 'test-nonce-abc',
|
|
46
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
47
|
+
createdAt: Date.now(),
|
|
48
|
+
lastActivity: Date.now(),
|
|
49
|
+
ttlMinutes: 30,
|
|
50
|
+
identityState: 'authenticated',
|
|
51
|
+
agentDid,
|
|
52
|
+
...overrides,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createTestDelegation(overrides: Partial<DelegationRecord> = {}): DelegationRecord {
|
|
57
|
+
return {
|
|
58
|
+
id: 'del-test-123',
|
|
59
|
+
issuerDid: serverDid,
|
|
60
|
+
subjectDid: agentDid,
|
|
61
|
+
vcId: 'urn:uuid:vc-test-456',
|
|
62
|
+
constraints: {
|
|
63
|
+
scopes: ['tool:execute', 'resource:read'],
|
|
64
|
+
},
|
|
65
|
+
signature: 'test-signature',
|
|
66
|
+
status: 'active',
|
|
67
|
+
createdAt: Date.now(),
|
|
68
|
+
...overrides,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createTestContext(
|
|
73
|
+
overrides: Partial<OutboundDelegationContext> = {}
|
|
74
|
+
): OutboundDelegationContext {
|
|
75
|
+
return {
|
|
76
|
+
session: createTestSession(),
|
|
77
|
+
delegation: createTestDelegation(),
|
|
78
|
+
serverIdentity: {
|
|
79
|
+
did: serverDid,
|
|
80
|
+
kid: serverKid,
|
|
81
|
+
privateKey: serverKeyPair.privateKey,
|
|
82
|
+
},
|
|
83
|
+
targetUrl: 'https://downstream-api.example.com/resource',
|
|
84
|
+
...overrides,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Tests
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
describe('buildOutboundDelegationHeaders', () => {
|
|
93
|
+
it('builds correct headers from a valid session + delegation', async () => {
|
|
94
|
+
const context = createTestContext();
|
|
95
|
+
const headers = await buildOutboundDelegationHeaders(context, cryptoProvider);
|
|
96
|
+
|
|
97
|
+
expect(headers).toHaveProperty('X-Agent-DID');
|
|
98
|
+
expect(headers).toHaveProperty('X-Delegation-Chain');
|
|
99
|
+
expect(headers).toHaveProperty('X-Session-ID');
|
|
100
|
+
expect(headers).toHaveProperty('X-Delegation-Proof');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('X-Agent-DID matches session.agentDid', async () => {
|
|
104
|
+
const context = createTestContext();
|
|
105
|
+
const headers = await buildOutboundDelegationHeaders(context, cryptoProvider);
|
|
106
|
+
|
|
107
|
+
expect(headers['X-Agent-DID']).toBe(context.session.agentDid);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('X-Delegation-Chain matches delegation.vcId', async () => {
|
|
111
|
+
const context = createTestContext();
|
|
112
|
+
const headers = await buildOutboundDelegationHeaders(context, cryptoProvider);
|
|
113
|
+
|
|
114
|
+
expect(headers['X-Delegation-Chain']).toBe(context.delegation.vcId);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('X-Session-ID matches session.sessionId', async () => {
|
|
118
|
+
const context = createTestContext();
|
|
119
|
+
const headers = await buildOutboundDelegationHeaders(context, cryptoProvider);
|
|
120
|
+
|
|
121
|
+
expect(headers['X-Session-ID']).toBe(context.session.sessionId);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('X-Delegation-Proof is a valid JWT with correct claims', async () => {
|
|
125
|
+
const context = createTestContext();
|
|
126
|
+
const headers = await buildOutboundDelegationHeaders(context, cryptoProvider);
|
|
127
|
+
|
|
128
|
+
const jwt = headers['X-Delegation-Proof'];
|
|
129
|
+
|
|
130
|
+
// Verify it's a valid JWT format (3 parts)
|
|
131
|
+
expect(jwt.split('.')).toHaveLength(3);
|
|
132
|
+
|
|
133
|
+
// Verify header
|
|
134
|
+
const header = decodeProtectedHeader(jwt);
|
|
135
|
+
expect(header.alg).toBe('EdDSA');
|
|
136
|
+
expect(header.kid).toBe(serverKid);
|
|
137
|
+
|
|
138
|
+
// Verify payload claims
|
|
139
|
+
const payload = decodeJwt(jwt);
|
|
140
|
+
expect(payload.iss).toBe(serverDid); // server forwarding
|
|
141
|
+
expect(payload.sub).toBe(agentDid); // original agent
|
|
142
|
+
expect(payload.aud).toBe('downstream-api.example.com'); // target hostname
|
|
143
|
+
expect(payload.scope).toBe('delegation:propagate');
|
|
144
|
+
expect(typeof payload.iat).toBe('number');
|
|
145
|
+
expect(typeof payload.exp).toBe('number');
|
|
146
|
+
expect(typeof payload.jti).toBe('string');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('extracts hostname correctly from full URL for aud claim', async () => {
|
|
150
|
+
const context = createTestContext({
|
|
151
|
+
targetUrl: 'https://api.service.example.com:8443/v1/resource?query=test',
|
|
152
|
+
});
|
|
153
|
+
const headers = await buildOutboundDelegationHeaders(context, cryptoProvider);
|
|
154
|
+
|
|
155
|
+
const payload = decodeJwt(headers['X-Delegation-Proof']);
|
|
156
|
+
expect(payload.aud).toBe('api.service.example.com');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('works with http:// URLs', async () => {
|
|
160
|
+
const context = createTestContext({
|
|
161
|
+
targetUrl: 'http://internal-service.local/api',
|
|
162
|
+
});
|
|
163
|
+
const headers = await buildOutboundDelegationHeaders(context, cryptoProvider);
|
|
164
|
+
|
|
165
|
+
const payload = decodeJwt(headers['X-Delegation-Proof']);
|
|
166
|
+
expect(payload.aud).toBe('internal-service.local');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('works with https:// URLs', async () => {
|
|
170
|
+
const context = createTestContext({
|
|
171
|
+
targetUrl: 'https://secure.example.org/endpoint',
|
|
172
|
+
});
|
|
173
|
+
const headers = await buildOutboundDelegationHeaders(context, cryptoProvider);
|
|
174
|
+
|
|
175
|
+
const payload = decodeJwt(headers['X-Delegation-Proof']);
|
|
176
|
+
expect(payload.aud).toBe('secure.example.org');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('JWT exp is 60 seconds from iat', async () => {
|
|
180
|
+
const context = createTestContext();
|
|
181
|
+
const headers = await buildOutboundDelegationHeaders(context, cryptoProvider);
|
|
182
|
+
|
|
183
|
+
const payload = decodeJwt(headers['X-Delegation-Proof']);
|
|
184
|
+
expect((payload.exp as number) - (payload.iat as number)).toBe(60);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('throws when session is missing agentDid', async () => {
|
|
188
|
+
const context = createTestContext({
|
|
189
|
+
session: createTestSession({ agentDid: undefined }),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await expect(
|
|
193
|
+
buildOutboundDelegationHeaders(context, cryptoProvider)
|
|
194
|
+
).rejects.toThrow('Session must have agentDid');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('throws when session is missing sessionId', async () => {
|
|
198
|
+
const context = createTestContext({
|
|
199
|
+
session: createTestSession({ sessionId: '' }),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
await expect(
|
|
203
|
+
buildOutboundDelegationHeaders(context, cryptoProvider)
|
|
204
|
+
).rejects.toThrow('Session must have sessionId');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('throws when delegation is missing vcId', async () => {
|
|
208
|
+
const context = createTestContext({
|
|
209
|
+
delegation: createTestDelegation({ vcId: '' }),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await expect(
|
|
213
|
+
buildOutboundDelegationHeaders(context, cryptoProvider)
|
|
214
|
+
).rejects.toThrow('Delegation must have vcId');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('throws for non-did:key server DID', async () => {
|
|
218
|
+
const context = createTestContext({
|
|
219
|
+
serverIdentity: {
|
|
220
|
+
did: 'did:web:server.example.com',
|
|
221
|
+
kid: 'did:web:server.example.com#key-1',
|
|
222
|
+
privateKey: serverKeyPair.privateKey,
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
await expect(
|
|
227
|
+
buildOutboundDelegationHeaders(context, cryptoProvider)
|
|
228
|
+
).rejects.toThrow('Server DID must be did:key with Ed25519');
|
|
229
|
+
});
|
|
230
|
+
});
|