Skip to content

Commit edb3f0f

Browse files
authored
Merge branch 'main' into feature/wal-1186
2 parents 34080df + c70da10 commit edb3f0f

7 files changed

Lines changed: 229 additions & 163 deletions

File tree

waltid-applications/mobile-e2e-fixtures/ios/TestHelpers/TestHelpers.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ public func jsonString(_ object: Any) throws -> String {
1010
public func buildDcqlQuery(credentialID: String) -> [String: Any] {
1111
let normalized = credentialID.lowercased()
1212

13+
if normalized.contains("ehic") && (normalized.contains("sd_jwt") || normalized.contains("jwt_vc")) {
14+
return [
15+
"credentials": [[
16+
"id": "query_0",
17+
"format": "dc+sd-jwt",
18+
"meta": ["vct_values": ["urn:eudi:ehic:1"]],
19+
]],
20+
]
21+
}
22+
1323
if normalized.contains("sd_jwt") || normalized.contains("jwt_vc") {
1424
return [
1525
"credentials": [[

waltid-applications/waltid-wallet-demo-ios/iosApp/iosAppTests/MobileWalletIntegrationTests.swift

Lines changed: 97 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import WalletSDK
77
///
88
/// Tests the Swift package facade that iOS apps consume directly.
99
/// Uses real iOS Keychain crypto, SQLDelight persistence, and OID4VCI/VP protocol
10-
/// against the public EUDI test backend.
10+
/// against public walt.id demo and EUDI test backends.
1111
///
1212
/// These are integration tests (not E2E UI tests) - they test the library directly
1313
/// without UI automation.
1414
final class MobileWalletIntegrationTests: XCTestCase {
1515

1616
private let testWalletId = "ios-unit-test-wallet"
17+
private static let eudiPidSdJwtCredentialID = "eu.europa.ec.eudi.pid_vc_sd_jwt"
18+
private static let eudiEhicSdJwtCredentialID = "eu.europa.ec.eudi.ehic_sd_jwt_vc"
1719
private static let demoTransactionDataProfiles: [WalletTransactionDataProfile] = [
1820
WalletTransactionDataProfile(
1921
type: "org.waltid.transaction-data.payment-authorization",
@@ -174,11 +176,11 @@ final class MobileWalletIntegrationTests: XCTestCase {
174176
XCTAssertEqual(deletedKeys, ["\(testWalletId):wallet_\(testWalletId)"])
175177
}
176178

177-
func testReceiveCredentialFromEudi() async throws {
179+
func testReceiveEudiPidSdJwtFromEudi() async throws {
178180
let wallet = try await makeWallet()
179181
_ = try await wallet.bootstrap()
180182

181-
let offer = try await EudiTestBackend.shared.generateOffer()
183+
let offer = try await EudiTestBackend.shared.generateOffer(credentialId: Self.eudiPidSdJwtCredentialID)
182184
let offerURL = try XCTUnwrap(URL(string: offer.offerUrl))
183185
let resolution = try await wallet.resolveOffer(offer: offerURL)
184186
XCTAssertTrue(resolution.transactionCodeRequired, "EUDI offer should require a transaction code")
@@ -199,75 +201,28 @@ final class MobileWalletIntegrationTests: XCTestCase {
199201
try await receiveCredentialFromDemoIssuer2(scenarioID: "iso-mdl")
200202
}
201203

202-
func testReceiveAndPresentFullFlow() async throws {
203-
let wallet = try await makeWallet()
204-
205-
let bootstrapResult = try await wallet.bootstrap()
206-
let did = bootstrapResult.did
207-
208-
let offer = try await EudiTestBackend.shared.generateOffer()
209-
let offerURL = try XCTUnwrap(URL(string: offer.offerUrl))
210-
let credentialIDs = try await wallet.receive(offer: offerURL, txCode: offer.txCode)
211-
XCTAssertFalse(credentialIDs.isEmpty, "Should receive at least one credential")
212-
213-
let credentials = try await wallet.credentials()
214-
XCTAssertFalse(credentials.isEmpty, "Should have stored credentials")
215-
216-
let credentialId = await EudiTestBackend.shared.extractCredentialIdFromOfferUrl(offerUrl: offer.offerUrl)
217-
let transaction = try await EudiTestBackend.shared.createVerifierTransaction(credentialId: credentialId)
218-
let presentationURL = try XCTUnwrap(URL(string: transaction.authorizationRequestUri))
219-
220-
let presentResult = try await wallet.present(
221-
request: presentationURL,
222-
did: did
223-
)
224-
225-
XCTAssertTrue(
226-
presentResult.success,
227-
"Presentation should succeed. Credentials: \(credentials), Result: \(presentResult)"
228-
)
229-
230-
// Wait for verifier to confirm receipt
231-
try await TestHelpers.waitForVerifierSuccess(transactionID: transaction.transactionId, timeoutSeconds: verifierPollingTimeout)
204+
func testReceiveAndPresentEudiEhicSdJwtAgainstEudi() async throws {
205+
try await receiveAndPresentEudiCredential(credentialID: Self.eudiEhicSdJwtCredentialID)
232206
}
233207

234-
func testPreviewAndSubmitFullFlowAgainstEudi() async throws {
235-
let walletId = "ios-eudi-preview-submit-\(UUID().uuidString)"
236-
await clearTestData(walletId: walletId)
237-
238-
let wallet = try await makeWallet(walletId: walletId)
239-
let bootstrapResult = try await wallet.bootstrap()
240-
let did = bootstrapResult.did
241-
242-
let offer = try await EudiTestBackend.shared.generateOffer()
243-
let offerURL = try XCTUnwrap(URL(string: offer.offerUrl))
244-
let credentialIDs = try await wallet.receive(offer: offerURL, txCode: offer.txCode)
245-
XCTAssertFalse(credentialIDs.isEmpty, "Should receive at least one EUDI credential")
208+
func testPreviewAndSubmitEudiEhicSdJwtAgainstEudi() async throws {
209+
try await previewAndSubmitEudiCredential(credentialID: Self.eudiEhicSdJwtCredentialID)
210+
}
246211

247-
let credentialId = await EudiTestBackend.shared.extractCredentialIdFromOfferUrl(offerUrl: offer.offerUrl)
248-
let transaction = try await EudiTestBackend.shared.createVerifierTransaction(credentialId: credentialId)
249-
let presentationURL = try XCTUnwrap(URL(string: transaction.authorizationRequestUri))
250-
let preview = try await wallet.previewPresentation(request: presentationURL)
251-
XCTAssertFalse(
252-
preview.credentialOptions.isEmpty,
253-
"Should preview at least one matching EUDI credential: \(preview)"
254-
)
255-
XCTAssertTrue(
256-
preview.credentialOptions.allSatisfy { credentialIDs.contains($0.credentialID) },
257-
"Preview should only offer credentials received in this test. Received: \(credentialIDs), Preview: \(preview)"
212+
func testReceiveAndPresentEudiPidSdJwtAgainstEudi() async throws {
213+
try XCTSkipIf(
214+
true,
215+
"Upstream issue: https://github.com/eu-digital-identity-wallet/eudi-srv-web-issuing-eudiw-py/issues/172"
258216
)
217+
try await receiveAndPresentEudiCredential(credentialID: Self.eudiPidSdJwtCredentialID)
218+
}
259219

260-
let result = try await wallet.submitPresentation(
261-
request: presentationURL,
262-
selectedCredentialOptions: preview.credentialOptions.map(\.selection),
263-
did: did
220+
func testPreviewAndSubmitEudiPidSdJwtAgainstEudi() async throws {
221+
try XCTSkipIf(
222+
true,
223+
"Upstream issue: https://github.com/eu-digital-identity-wallet/eudi-srv-web-issuing-eudiw-py/issues/172"
264224
)
265-
XCTAssertTrue(
266-
result.success,
267-
"EUDI stepwise presentation should succeed. Preview: \(preview), Result: \(result)"
268-
)
269-
270-
try await TestHelpers.waitForVerifierSuccess(transactionID: transaction.transactionId, timeoutSeconds: verifierPollingTimeout)
225+
try await previewAndSubmitEudiCredential(credentialID: Self.eudiPidSdJwtCredentialID)
271226
}
272227

273228
func testReceiveAndPresentEudiPidSdJwtAgainstDemoIssuer2AndVerifier2() async throws {
@@ -290,39 +245,23 @@ final class MobileWalletIntegrationTests: XCTestCase {
290245
try await receiveAndPresentDemoCredential(scenarioID: "iso-mdl")
291246
}
292247

293-
func testCredentialPersistsAcrossControllerRecreation() async throws {
294-
let wallet1 = try await makeWallet()
295-
296-
let bootstrapResult = try await wallet1.bootstrap()
297-
let did = bootstrapResult.did
248+
func testEudiPidSdJwtPersistsAcrossControllerRecreation() async throws {
249+
let walletId = "ios-eudi-pid-sd-jwt-persistence-\(UUID().uuidString)"
250+
await clearTestData(walletId: walletId)
251+
let wallet1 = try await makeWallet(walletId: walletId)
252+
_ = try await wallet1.bootstrap()
298253

299-
let offer = try await EudiTestBackend.shared.generateOffer()
254+
let offer = try await EudiTestBackend.shared.generateOffer(credentialId: Self.eudiPidSdJwtCredentialID)
300255
let offerURL = try XCTUnwrap(URL(string: offer.offerUrl))
301256
let credentialIDs = try await wallet1.receive(offer: offerURL, txCode: offer.txCode)
302257
XCTAssertFalse(credentialIDs.isEmpty, "Should receive at least one credential")
303258

304259
// Recreate wallet facade (simulates app restart)
305-
let wallet2 = try await makeWallet()
260+
let wallet2 = try await makeWallet(walletId: walletId)
306261

307262
_ = try await wallet2.bootstrap()
308263
let credentials = try await wallet2.credentials()
309264
XCTAssertFalse(credentials.isEmpty, "Credentials should persist across controller recreation")
310-
311-
let credentialId = await EudiTestBackend.shared.extractCredentialIdFromOfferUrl(offerUrl: offer.offerUrl)
312-
let transaction = try await EudiTestBackend.shared.createVerifierTransaction(credentialId: credentialId)
313-
let presentationURL = try XCTUnwrap(URL(string: transaction.authorizationRequestUri))
314-
315-
let presentResult = try await wallet2.present(
316-
request: presentationURL,
317-
did: did
318-
)
319-
320-
XCTAssertTrue(
321-
presentResult.success,
322-
"Should present from persisted credentials. Credentials: \(credentials), Result: \(presentResult)"
323-
)
324-
325-
try await TestHelpers.waitForVerifierSuccess(transactionID: transaction.transactionId, timeoutSeconds: verifierPollingTimeout)
326265
}
327266

328267
func testDemoCredentialPersistsAcrossControllerRecreation() async throws {
@@ -366,6 +305,75 @@ final class MobileWalletIntegrationTests: XCTestCase {
366305
)
367306
}
368307

308+
private func receiveAndPresentEudiCredential(credentialID: String) async throws {
309+
let walletId = "ios-eudi-present-\(UUID().uuidString)"
310+
await clearTestData(walletId: walletId)
311+
let wallet = try await makeWallet(walletId: walletId)
312+
let bootstrapResult = try await wallet.bootstrap()
313+
314+
let offer = try await EudiTestBackend.shared.generateOffer(credentialId: credentialID)
315+
let offerURL = try XCTUnwrap(URL(string: offer.offerUrl))
316+
let credentialIDs = try await wallet.receive(offer: offerURL, txCode: offer.txCode)
317+
XCTAssertFalse(credentialIDs.isEmpty, "Should receive EUDI credential \(credentialID)")
318+
319+
let credentials = try await wallet.credentials()
320+
XCTAssertFalse(credentials.isEmpty, "Should store EUDI credential \(credentialID)")
321+
322+
let offeredCredentialID = await EudiTestBackend.shared.extractCredentialIdFromOfferUrl(offerUrl: offer.offerUrl)
323+
let transaction = try await EudiTestBackend.shared.createVerifierTransaction(credentialId: offeredCredentialID)
324+
let presentationURL = try XCTUnwrap(URL(string: transaction.authorizationRequestUri))
325+
let result = try await wallet.present(request: presentationURL, did: bootstrapResult.did)
326+
XCTAssertTrue(
327+
result.success,
328+
"EUDI presentation should succeed for \(credentialID). Credentials: \(credentials), Result: \(result)"
329+
)
330+
331+
try await TestHelpers.waitForVerifierSuccess(
332+
transactionID: transaction.transactionId,
333+
timeoutSeconds: verifierPollingTimeout
334+
)
335+
}
336+
337+
private func previewAndSubmitEudiCredential(credentialID: String) async throws {
338+
let walletId = "ios-eudi-preview-submit-\(UUID().uuidString)"
339+
await clearTestData(walletId: walletId)
340+
let wallet = try await makeWallet(walletId: walletId)
341+
let bootstrapResult = try await wallet.bootstrap()
342+
343+
let offer = try await EudiTestBackend.shared.generateOffer(credentialId: credentialID)
344+
let offerURL = try XCTUnwrap(URL(string: offer.offerUrl))
345+
let credentialIDs = try await wallet.receive(offer: offerURL, txCode: offer.txCode)
346+
XCTAssertFalse(credentialIDs.isEmpty, "Should receive EUDI credential \(credentialID)")
347+
348+
let offeredCredentialID = await EudiTestBackend.shared.extractCredentialIdFromOfferUrl(offerUrl: offer.offerUrl)
349+
let transaction = try await EudiTestBackend.shared.createVerifierTransaction(credentialId: offeredCredentialID)
350+
let presentationURL = try XCTUnwrap(URL(string: transaction.authorizationRequestUri))
351+
let preview = try await wallet.previewPresentation(request: presentationURL)
352+
XCTAssertFalse(
353+
preview.credentialOptions.isEmpty,
354+
"Should preview a matching EUDI credential for \(credentialID): \(preview)"
355+
)
356+
XCTAssertTrue(
357+
preview.credentialOptions.allSatisfy { credentialIDs.contains($0.credentialID) },
358+
"Preview should only offer credentials received in this test. Received: \(credentialIDs), Preview: \(preview)"
359+
)
360+
361+
let result = try await wallet.submitPresentation(
362+
request: presentationURL,
363+
selectedCredentialOptions: preview.credentialOptions.map(\.selection),
364+
did: bootstrapResult.did
365+
)
366+
XCTAssertTrue(
367+
result.success,
368+
"EUDI stepwise presentation should succeed for \(credentialID). Preview: \(preview), Result: \(result)"
369+
)
370+
371+
try await TestHelpers.waitForVerifierSuccess(
372+
transactionID: transaction.transactionId,
373+
timeoutSeconds: verifierPollingTimeout
374+
)
375+
}
376+
369377
private func previewAndSubmitDemoCredential(scenarioID: String) async throws {
370378
let scenario = try demoPresentationScenario(scenarioID)
371379
let walletId = "ios-demo-preview-submit-\(scenario.id)-\(UUID().uuidString)"

waltid-libraries/protocols/waltid-mobile-test-utils/src/commonMain/kotlin/id/walt/mobile/test/backend/EudiTestBackend.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,20 @@ object EudiTestBackend {
199199
}
200200
}
201201

202-
private fun buildDcqlQuery(credentialId: String): JsonElement {
202+
internal fun buildDcqlQuery(credentialId: String): JsonElement {
203203
val format: String
204204
val meta: JsonObject
205205

206206
when {
207+
credentialId.contains("ehic") &&
208+
(credentialId.contains("sd_jwt") || credentialId.contains("jwt_vc")) -> {
209+
format = "dc+sd-jwt"
210+
meta = buildJsonObject {
211+
putJsonArray("vct_values") {
212+
add("urn:eudi:ehic:1")
213+
}
214+
}
215+
}
207216
credentialId.contains("sd_jwt") || credentialId.contains("jwt_vc") -> {
208217
format = "dc+sd-jwt"
209218
meta = buildJsonObject {

waltid-libraries/protocols/waltid-mobile-test-utils/src/commonTest/kotlin/id/walt/mobile/test/backend/EudiTestBackendTest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,28 @@ package id.walt.mobile.test.backend
33
import io.ktor.http.encodeURLParameter
44
import kotlinx.serialization.json.JsonPrimitive
55
import kotlinx.serialization.json.buildJsonObject
6+
import kotlinx.serialization.json.jsonArray
7+
import kotlinx.serialization.json.jsonObject
8+
import kotlinx.serialization.json.jsonPrimitive
69
import kotlinx.serialization.json.put
710
import kotlin.test.Test
811
import kotlin.test.assertEquals
912
import kotlin.test.assertFailsWith
1013

1114
class EudiTestBackendTest {
15+
@Test
16+
fun buildsEhicSdJwtVerifierQuery() {
17+
val query = EudiTestBackend.buildDcqlQuery("eu.europa.ec.eudi.ehic_sd_jwt_vc").jsonObject
18+
val credentialQuery = query.getValue("credentials").jsonArray.single().jsonObject
19+
20+
assertEquals("dc+sd-jwt", credentialQuery.getValue("format").jsonPrimitive.content)
21+
assertEquals(
22+
listOf("urn:eudi:ehic:1"),
23+
credentialQuery.getValue("meta").jsonObject.getValue("vct_values").jsonArray
24+
.map { it.jsonPrimitive.content },
25+
)
26+
}
27+
1228
@Test
1329
fun preservesStandardOfferAndReturnsTransactionCodeSeparately() {
1430
val offerJson = """

0 commit comments

Comments
 (0)