Skip to content

Commit 42b3f84

Browse files
committed
e2e test for credential import
1 parent f3f46c8 commit 42b3f84

4 files changed

Lines changed: 114 additions & 11 deletions

File tree

apps/wallet-server/jest.setup.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,15 @@ jest.mock('@docknetwork/wallet-sdk-wasm/src/services/blockchain', () => ({
77
isBlockchainReady: true,
88
},
99
}));
10+
11+
// The wallet SDK can emit late debug logs from background status updates.
12+
// Keep integration tests deterministic by silencing data-store logger output.
13+
jest.mock('@docknetwork/wallet-sdk-data-store/src/logger', () => ({
14+
logger: {
15+
debug: jest.fn(),
16+
performance: jest.fn(),
17+
error: jest.fn(),
18+
warn: jest.fn(),
19+
info: jest.fn(),
20+
},
21+
}));

apps/wallet-server/src/features/credentials/tests/integration/credentials-client.test.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@ import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
77

88
import { WalletClient } from "../../../../wallet-client";
99
import { CredentialClient } from "../../client";
10+
import { DIDClient } from "../../../dids/client";
11+
12+
const OID4VCI_TEST_OFFER_URI =
13+
"openid-credential-offer://?credential_offer_uri=https://api-testnet.truvera.io/openid/credential-offers/fbe0a6ed-2aa8-4363-833b-6aa8fe1c6d01";
1014

1115
describe("integration: CredentialClient with real Wallet SDK", () => {
1216
let walletClient: WalletClient;
1317
let credentialClient: CredentialClient;
18+
let didClient: DIDClient;
1419

1520
beforeEach(async () => {
1621
// Create a unique wallet for each test to avoid conflicts
@@ -20,19 +25,23 @@ describe("integration: CredentialClient with real Wallet SDK", () => {
2025
// Initialize wallet and credential client
2126
const wallet = await walletClient.initialize();
2227
credentialClient = new CredentialClient(wallet);
28+
didClient = new DIDClient(wallet);
2329
});
2430

2531
afterEach(async () => {
32+
if (walletClient) {
33+
await walletClient.waitForIdle();
34+
}
35+
2636
// Clean up wallet resources after each test
2737
if (walletClient && walletClient.isInitialized()) {
2838
try {
2939
await walletClient.deleteWallet();
40+
await walletClient.waitForIdle();
3041
} catch (error) {
3142
console.error("Error cleaning up wallet:", error);
3243
}
3344
}
34-
// Wait for remaining async operations to complete
35-
await new Promise((resolve) => setTimeout(resolve, 100));
3645
});
3746

3847
describe("listCredentials", () => {
@@ -133,5 +142,34 @@ describe("integration: CredentialClient with real Wallet SDK", () => {
133142
// message is present on failure
134143
expect(result).toHaveProperty("message");
135144
});
145+
146+
it("imports credential from OID4VCI offer URL and stores it in the wallet SDK", async () => {
147+
// Ensure the wallet has a DID available before import.
148+
await didClient.createDID();
149+
150+
const before = await credentialClient.listCredentials();
151+
152+
const importResult = await credentialClient.importCredential(OID4VCI_TEST_OFFER_URI);
153+
expect(importResult.success).toBe(true);
154+
expect(importResult.credential).toBeDefined();
155+
156+
const after = await credentialClient.listCredentials();
157+
expect(after.count).toBeGreaterThan(before.count);
158+
159+
// Verify the imported credential is visible via wallet-sdk provider storage too.
160+
const { createCredentialProvider } = await import("@docknetwork/wallet-sdk-core/lib/credential-provider.js");
161+
const provider = createCredentialProvider({ wallet: walletClient.getWallet() });
162+
const sdkCredentials = await provider.getCredentials();
163+
expect(sdkCredentials.length).toBeGreaterThan(0);
164+
165+
const importedId = importResult.credential?.id;
166+
if (importedId) {
167+
const inSdkStore = sdkCredentials.some((doc: any) => {
168+
const id = doc?.id || doc?.credential?.id;
169+
return id === importedId;
170+
});
171+
expect(inSdkStore).toBe(true);
172+
}
173+
});
136174
});
137175
});

apps/wallet-server/src/features/dids/tests/integration/dids-client.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ describe("integration: DIDClient with real Wallet SDK", () => {
2323
});
2424

2525
afterEach(async () => {
26+
if (walletClient) {
27+
await walletClient.waitForIdle();
28+
}
29+
2630
// Clean up wallet resources after each test
2731
if (walletClient && walletClient.isInitialized()) {
2832
try {
@@ -31,8 +35,6 @@ describe("integration: DIDClient with real Wallet SDK", () => {
3135
console.error("Error cleaning up wallet:", error);
3236
}
3337
}
34-
// Wait for remaining async operations to complete
35-
await new Promise((resolve) => setTimeout(resolve, 100));
3638
});
3739

3840
describe("getDefaultDID", () => {

apps/wallet-server/src/wallet-client.ts

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,64 @@ export class WalletClient {
4545
private walletName: string;
4646
private networkId: string;
4747
private dataStore: DataStore | null = null;
48+
private inFlightDocumentOps = 0;
49+
private idleWaiters: Array<() => void> = [];
4850

4951
constructor(walletName: string = "default-wallet", networkId: string = "testnet") {
5052
this.walletName = walletName;
5153
this.networkId = networkId;
5254
}
5355

56+
private trackDocumentOperation<T>(operation: Promise<T>): Promise<T> {
57+
this.inFlightDocumentOps += 1;
58+
59+
return operation.finally(() => {
60+
this.inFlightDocumentOps = Math.max(0, this.inFlightDocumentOps - 1);
61+
62+
if (this.inFlightDocumentOps === 0) {
63+
const waiters = this.idleWaiters.splice(0);
64+
for (const resolve of waiters) {
65+
resolve();
66+
}
67+
}
68+
});
69+
}
70+
71+
/**
72+
* Wait until tracked document operations settle.
73+
* This avoids brittle fixed sleeps in integration tests.
74+
*/
75+
async waitForIdle(timeoutMs: number = 10000): Promise<void> {
76+
const start = Date.now();
77+
78+
while (true) {
79+
if (this.inFlightDocumentOps === 0) {
80+
// Ensure no new operations were queued in the same tick.
81+
await new Promise<void>((resolve) => setImmediate(resolve));
82+
if (this.inFlightDocumentOps === 0) {
83+
return;
84+
}
85+
}
86+
87+
const elapsed = Date.now() - start;
88+
const remaining = timeoutMs - elapsed;
89+
if (remaining <= 0) {
90+
throw new Error(`Timed out waiting for wallet document operations to settle after ${timeoutMs}ms`);
91+
}
92+
93+
await new Promise<void>((resolve, reject) => {
94+
const timeoutId = setTimeout(() => {
95+
reject(new Error(`Timed out waiting for wallet document operations to settle after ${timeoutMs}ms`));
96+
}, remaining);
97+
98+
this.idleWaiters.push(() => {
99+
clearTimeout(timeoutId);
100+
resolve();
101+
});
102+
});
103+
}
104+
}
105+
54106
/**
55107
* Initialize the wallet
56108
*/
@@ -75,14 +127,14 @@ export class WalletClient {
75127
},
76128
dataSource,
77129
documentStore: {
78-
addDocument: (json, options) => createDocument({ dataStore, json, options }),
79-
removeDocument: (id, options) => removeDocument({ dataStore, id, options }),
80-
updateDocument: (document, options) => updateDocument({ dataStore, document, options }),
130+
addDocument: (json, options) => this.trackDocumentOperation(createDocument({ dataStore, json, options })),
131+
removeDocument: (id, options) => this.trackDocumentOperation(removeDocument({ dataStore, id, options })),
132+
updateDocument: (document, options) => this.trackDocumentOperation(updateDocument({ dataStore, document, options })),
81133
getDocumentById: (id) => getDocumentById({ dataStore, id }),
82134
getDocumentsByType: (type) => getDocumentsByType({ dataStore, type }),
83135
getDocumentsById: (idList) => getDocumentsById({ dataStore, idList }),
84136
getAllDocuments: (allNetworks) => getAllDocuments({ dataStore, allNetworks }),
85-
removeAllDocuments: () => removeAllDocuments({ dataStore }),
137+
removeAllDocuments: () => this.trackDocumentOperation(removeAllDocuments({ dataStore })),
86138
getDocumentCorrelations: (documentId) => getDocumentCorrelations({ dataStore, documentId }),
87139
},
88140
localStorageImpl,
@@ -161,14 +213,13 @@ export class WalletClient {
161213

162214
// Call wallet cleanup
163215
try {
216+
await this.waitForIdle();
164217
await this.wallet.deleteWallet();
218+
await this.waitForIdle();
165219
} catch (err) {
166220
console.debug("Error in wallet.deleteWallet():", err);
167221
}
168222

169-
// Wait for async operations to settle before cleanup
170-
await new Promise((resolve) => setTimeout(resolve, 100));
171-
172223
// Null out references to allow garbage collection
173224
this.wallet = null;
174225
this.dataStore = null;

0 commit comments

Comments
 (0)