diff --git a/integration-tests/e2e-tests/.env.example b/integration-tests/e2e-tests/.env.example index aa6d08221..279978c35 100644 --- a/integration-tests/e2e-tests/.env.example +++ b/integration-tests/e2e-tests/.env.example @@ -1,6 +1,6 @@ -MEDIATOR_OOB_URL= -AGENT_URL= -APIKEY= +MEDIATOR_OOB_URL=https://mediator.mydomain.com/invitationOOB +AGENT_URL=https://issuer.mydomain.com/cloud-agent/ +APIKEY=myDomainAPIKey PUBLISHED_DID= JWT_SCHEMA_GUID= ANONCRED_DEFINITION_GUID= \ No newline at end of file diff --git a/integration-tests/e2e-tests/README.md b/integration-tests/e2e-tests/README.md index 1da9d1e82..f4bee06d4 100644 --- a/integration-tests/e2e-tests/README.md +++ b/integration-tests/e2e-tests/README.md @@ -9,14 +9,14 @@ This guide shows you how to run the end-to-end tests - Copy `.env.example` to `.env` - Fill the required properties -| Property | Description | -| ------------------------ | ----------------------------------------------------------- | -| MEDIATOR_OOB_URL | URL that returns the OOB url | -| AGENT_URL | URL for Cloud Agent - should end with a forward slash ("/") | -| APIKEY | (Optional) Apikey authentication | -| PUBLISHED_DID | (Optional) Existing DID | -| JWT_SCHEMA_GUID | (Optional) Existing jwt schema guid | -| ANONCRED_DEFINITION_GUID | (Optional) Existing anoncred definition guid | +| Property | Description | +| ------------------------ | ------------------------------------------------------------------------------------------------------------| +| MEDIATOR_OOB_URL | URL that returns the OOB url (e.g. https://mediator.mydomain.com/invitationOOB) | +| AGENT_URL | URL for Cloud Agent - should end with a forward slash ("/") (e.g. https://issuer.mydomain.com/cloud-agent/) | +| APIKEY | (Optional) Apikey authentication | +| PUBLISHED_DID | (Optional) Existing DID | +| JWT_SCHEMA_GUID | (Optional) Existing jwt schema guid | +| ANONCRED_DEFINITION_GUID | (Optional) Existing anoncred definition guid | ### Compile the SDK diff --git a/integration-tests/e2e-tests/features/connectionless-credential-offer.feature b/integration-tests/e2e-tests/features/connectionless-credential-offer.feature new file mode 100644 index 000000000..ae96b002d --- /dev/null +++ b/integration-tests/e2e-tests/features/connectionless-credential-offer.feature @@ -0,0 +1,12 @@ +@connectionless @credential-offer +Feature: Edge SDK Connectionless Credential Offer + + Scenario: Receive a credential without a connection + Given Cloud Agent is not connected to Edge Agent + When Cloud Agent has a connectionless credential offer invitation + And Cloud Agent shares invitation to Edge Agent + Then Edge Agent accepts the connectionless credential offer invitation + Then Edge Agent should receive the connectionless credential offer + Then Edge Agent accepts the connectionless credential offer + Then Edge Agent should receive the connectionless credential + And Edge Agent processes the issued connectionless credential from Cloud Agent diff --git a/integration-tests/e2e-tests/src/abilities/WalletSdk.ts b/integration-tests/e2e-tests/src/abilities/WalletSdk.ts index 03ae50e6a..03bb44ca8 100644 --- a/integration-tests/e2e-tests/src/abilities/WalletSdk.ts +++ b/integration-tests/e2e-tests/src/abilities/WalletSdk.ts @@ -117,7 +117,8 @@ export class WalletSdk extends Ability implements Initialisable, Discardable { issuedCredentialStack: SDK.Domain.Message[] proofRequestStack: SDK.Domain.Message[] revocationStack: SDK.Domain.Message[], - presentationMessagesStack: SDK.Domain.Message[] + presentationMessagesStack: SDK.Domain.Message[], + enqueue(message: SDK.Domain.Message): Promise }) => Promise): Interaction { return Interaction.where("#actor uses wallet sdk", async actor => { @@ -126,7 +127,12 @@ export class WalletSdk extends Ability implements Initialisable, Discardable { issuedCredentialStack: WalletSdk.as(actor).messages.issuedCredentialStack, proofRequestStack: WalletSdk.as(actor).messages.proofRequestStack, revocationStack: WalletSdk.as(actor).messages.revocationStack, - presentationMessagesStack: WalletSdk.as(actor).messages.presentationMessagesStack + presentationMessagesStack: WalletSdk.as(actor).messages.presentationMessagesStack, + + enqueue: async (message: SDK.Domain.Message) => { + // Ensure to call the async method properly + await WalletSdk.as(actor).messages.enqueue(message); + } }) }) } diff --git a/integration-tests/e2e-tests/src/configuration/CloudAgentConfiguration.ts b/integration-tests/e2e-tests/src/configuration/CloudAgentConfiguration.ts index 0adf88fb0..645f0fb99 100644 --- a/integration-tests/e2e-tests/src/configuration/CloudAgentConfiguration.ts +++ b/integration-tests/e2e-tests/src/configuration/CloudAgentConfiguration.ts @@ -205,6 +205,7 @@ export class CloudAgentConfiguration { try { assert(this.anoncredDefinitionGuid != null) assert(this.anoncredDefinitionGuid != "") + await axiosInstance.get( `credential-definition-registry/definitions/${this.anoncredDefinitionGuid}` ) diff --git a/integration-tests/e2e-tests/src/steps/CloudAgentSteps.ts b/integration-tests/e2e-tests/src/steps/CloudAgentSteps.ts index 886dfc7bc..d5af72c6e 100644 --- a/integration-tests/e2e-tests/src/steps/CloudAgentSteps.ts +++ b/integration-tests/e2e-tests/src/steps/CloudAgentSteps.ts @@ -10,7 +10,8 @@ Given("{actor} has a connection invitation with '{}', '{}' and '{}' parameters", const goalCode = rawGoalCode == "null" ? undefined : rawGoalCode const goal = rawGoal == "null" ? undefined : rawGoal await CloudAgentWorkflow.createConnection(cloudAgent, label, goalCode, goal) - }) + } +) Given("{actor} is connected to {actor}", async function (cloudAgent: Actor, edgeAgent: Actor) { await CloudAgentWorkflow.createConnection(cloudAgent) @@ -56,10 +57,6 @@ When("{actor} offers '{int}' anonymous credential", async function (cloudAgent: ) }) -When("{actor} asks for sdjwt present-proof", async function (cloudAgent: Actor) { - await CloudAgentWorkflow.askForSDJWTPresentProof(cloudAgent) -}) - When("{actor} asks for present-proof", async function (cloudAgent: Actor) { await CloudAgentWorkflow.askForPresentProof(cloudAgent) }) @@ -92,9 +89,19 @@ Then("{actor} should see the present-proof is not verified", async (cloudAgent: await CloudAgentWorkflow.verifyPresentProof(cloudAgent, "PresentationFailed") }) + Then("{actor} should see all credentials were accepted", async (cloudAgent: Actor) => { const recordIdList = await cloudAgent.answer(Notepad.notes().get("recordIdList")) for (const recordId of recordIdList) { await CloudAgentWorkflow.verifyCredentialState(cloudAgent, recordId, "CredentialSent") } }) + +Given("{actor} is not connected to Edge Agent", async function (cloudAgent: Actor) { + await CloudAgentWorkflow.verifyNoConnection(cloudAgent) +}) + +Given("{actor} has a connectionless credential offer invitation", async function (cloudAgent: Actor) { + await CloudAgentWorkflow.createConnectionlessCredentialOfferInvitation(cloudAgent) +}) + diff --git a/integration-tests/e2e-tests/src/steps/EdgeAgentSteps.ts b/integration-tests/e2e-tests/src/steps/EdgeAgentSteps.ts index 7b787a10c..c7b308603 100644 --- a/integration-tests/e2e-tests/src/steps/EdgeAgentSteps.ts +++ b/integration-tests/e2e-tests/src/steps/EdgeAgentSteps.ts @@ -316,3 +316,49 @@ Then("{actor} should see the verification proof is verified false", async (edgeA await EdgeAgentWorkflow.waitForPresentationMessage(edgeAgent) await EdgeAgentWorkflow.verifyPresentation(edgeAgent, false) }) + + +When("{actor} accepts the connectionless credential offer invitation", + async function (edgeAgent: Actor) { + await EdgeAgentWorkflow.acceptCredentialOfferInvitation(edgeAgent) + } +) + +Then("{actor} should receive the connectionless credential offer", + async function (edgeAgent: Actor) { + try { + await EdgeAgentWorkflow.waitForCredentialOffer(edgeAgent, 1) + } catch (error) { + // NOTE: sometimes the listener fails, so we fall back to getting the messages from + // pluto + await EdgeAgentWorkflow.loadMessagesFromPluto(edgeAgent) + await EdgeAgentWorkflow.waitForCredentialOffer(edgeAgent, 1) + } + } +) + +When("{actor} accepts the connectionless credential offer", + async function (edgeAgent: Actor) { + await EdgeAgentWorkflow.acceptCredential(edgeAgent) + } +) + +Then("{actor} should receive the connectionless credential", + async function (edgeAgent: Actor) { + try { + await EdgeAgentWorkflow.waitToReceiveCredentialIssuance(edgeAgent, 1) + } catch (error) { + // NOTE: sometimes the listener fails, so we fall back to getting the messages from + // pluto + await EdgeAgentWorkflow.loadMessagesFromPluto(edgeAgent) + await EdgeAgentWorkflow.waitToReceiveCredentialIssuance(edgeAgent, 1) + } + } +) + +Then("{actor} processes the issued connectionless credential from {actor}", + async function (edgeAgent: Actor, cloudAgent: Actor) { + const recordId = await cloudAgent.answer(Notepad.notes().get("recordId")) + await EdgeAgentWorkflow.processIssuedCredential(edgeAgent, recordId) + } +) diff --git a/integration-tests/e2e-tests/src/workflow/CloudAgentWorkflow.ts b/integration-tests/e2e-tests/src/workflow/CloudAgentWorkflow.ts index 00e4bac9a..b304758c6 100644 --- a/integration-tests/e2e-tests/src/workflow/CloudAgentWorkflow.ts +++ b/integration-tests/e2e-tests/src/workflow/CloudAgentWorkflow.ts @@ -54,6 +54,16 @@ export class CloudAgentWorkflow { ) } + static async verifyNoConnection(cloudAgent: Actor) { + const connectionId = await cloudAgent.answer( + Notepad.notes().get("connectionId") + ).catch(() => null) + + await cloudAgent.attemptsTo( + Ensure.that(connectionId, equals(null)) + ) + } + static async verifyCredentialState(cloudAgent: Actor, recordId: string, state: string) { await cloudAgent.attemptsTo( Wait.upTo(Duration.ofSeconds(60)).until( @@ -75,6 +85,36 @@ export class CloudAgentWorkflow { ) } + static async createConnectionlessCredentialOfferInvitation(cloudAgent: Actor) { + const credentialOffer = { + claims: { + emailAddress: "sampleEmail", + familyName: "", + dateOfIssuance: "2023-01-01T02:02:02Z", + drivingLicenseID: "", + drivingClass: 1, + }, + goalCode: "issue-vc", + goal: "Request issuance", + credentialFormat: "JWT", + issuingDID: CloudAgentConfiguration.publishedDid, + automaticIssuance: true + } + + await cloudAgent.attemptsTo( + Send.a(PostRequest.to("issue-credentials/credential-offers/invitation").with(credentialOffer)), + Ensure.that(LastResponse.status(), equals(HttpStatusCode.Created)), + Notepad.notes().set( + "invitation", + LastResponse.body().invitation.invitationUrl + ), + Notepad.notes().set( + "recordId", + LastResponse.body().recordId + ) + ) + } + static async offerCredential(cloudAgent: Actor) { const credential = new CreateIssueCredentialRecordRequest() credential.claims = { diff --git a/integration-tests/e2e-tests/src/workflow/EdgeAgentWorkflow.ts b/integration-tests/e2e-tests/src/workflow/EdgeAgentWorkflow.ts index e1b9d7a78..e491454af 100644 --- a/integration-tests/e2e-tests/src/workflow/EdgeAgentWorkflow.ts +++ b/integration-tests/e2e-tests/src/workflow/EdgeAgentWorkflow.ts @@ -23,6 +23,10 @@ export class EdgeAgentWorkflow { ) } + static async acceptCredentialOfferInvitation(edgeAgent: Actor): Promise { + await this.connect(edgeAgent) + } + static async waitForCredentialOffer(edgeAgent: Actor, numberOfCredentialOffer: number) { await edgeAgent.attemptsTo( Wait.upTo(Duration.ofSeconds(60)).until( @@ -32,6 +36,20 @@ export class EdgeAgentWorkflow { ) } + // NOTE: sometimes the listener fails, so we have to fallback to + // the messages in pluto + static async loadMessagesFromPluto (edgeAgent: Actor) { + await edgeAgent.attemptsTo( + WalletSdk.execute(async (sdk, messages) => { + const msgs = await sdk.pluto.getAllMessages() + + await Promise.all( + msgs.map(msg => messages.enqueue(msg)) + ) + }) + ) + } + static async waitToReceiveCredentialIssuance(edgeAgent: Actor, expectedNumberOfCredentials: number) { await edgeAgent.attemptsTo( Wait.upTo(Duration.ofSeconds(60)).until(