Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests: add integration tests for credential schemas #236

Merged
merged 13 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 65 additions & 6 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
run-e2e-tests:
name: "Run e2e tests"
runs-on: ubuntu-latest
env:
REPORTS_DIR: "tests/e2e-tests/target/site/serenity"
steps:
- name: Checkout
uses: actions/checkout@v3
Expand All @@ -34,6 +36,9 @@ jobs:
with:
java-version: [email protected]

- name: Setup Gradle
uses: gradle/gradle-build-action@v2

- name: Install Python
uses: actions/setup-python@v2
with:
Expand Down Expand Up @@ -72,25 +77,79 @@ jobs:
up-flags: "--wait"
down-flags: "--volumes"

- name: Start services for verifier
env:
PORT: 8070
uses: isbang/[email protected]
with:
compose-file: "./infrastructure/shared/docker-compose.yml"
compose-flags: "--env-file ./infrastructure/local/.env -p verifier"
up-flags: "--wait"
down-flags: "--volumes"

- name: Run e2e tests
continue-on-error: true
env:
ATALA_GITHUB_TOKEN: ${{ secrets.ATALA_GITHUB_TOKEN }}
run: |
../../infrastructure/local/update_env.sh
cat ../../infrastructure/local/.env
./gradlew test --tests "E2eTestsRunner"
./gradlew test --tests "E2eTestsRunner" || true
./gradlew reports

- uses: actions/upload-artifact@v2
if: always()
with:
name: e2e-tests-result
path: tests/e2e-tests/target/site/serenity
path: ${{ env.REPORTS_DIR }}

- name: Publish e2e test Results
if: always()
id: publish-unit-tests
uses: EnricoMi/publish-unit-test-result-action@v2
with:
junit_files: "tests/e2e-tests/target/site/serenity/SERENITY-JUNIT-*.xml"
junit_files: "${{ env.REPORTS_DIR }}/SERENITY-JUNIT-*.xml"
comment_title: "E2E Test Results"
check_name: "E2E Test Results"

- name: Extract test results
id: analyze_test_results
if: github.ref_name == 'master'
run: |
JSON_RESULTS="target/site/serenity/serenity-summary.json"
CONCLUSION=failure
TOTAL_TESTS=0
FAILED_TESTS=0
SKIPPED_TESTS=0
TESTS_WITH_ERRORS=0
if [ -f "${JSON_RESULTS}" ]; then
TOTAL_TESTS="$(cat ${JSON_RESULTS} | jq '.results.counts.total')"
SUCCESS_TESTS="$(cat ${JSON_RESULTS} | jq '.results.counts.success')"
PENDING_TESTS="$(cat ${JSON_RESULTS} | jq '.results.counts.pending')"
SKIPPED_TESTS="$(cat ${JSON_RESULTS} | jq '.results.counts.skipped')"
IGNORED_TESTS="$(cat ${JSON_RESULTS} | jq '.results.counts.ignored')"
FAILED_TESTS="$(cat ${JSON_RESULTS} | jq '.results.counts.failure')"
TESTS_WITH_ERRORS="$(cat ${JSON_RESULTS} | jq '.results.counts.error')"
if [[ ${FAILED_TESTS} == 0 && ${TESTS_WITH_ERRORS} == 0 ]] ; then
CONCLUSION=success
fi
fi
echo "conclusion=${CONCLUSION}" >> $GITHUB_OUTPUT
echo "tests=${TOTAL_TESTS}" >> $GITHUB_OUTPUT
echo "failures=${FAILED_TESTS}" >> $GITHUB_OUTPUT
echo "errors=${TESTS_WITH_ERRORS}" >> $GITHUB_OUTPUT
echo "pending=${PENDING_TESTS}" >> $GITHUB_OUTPUT
echo "skipped=${SKIPPED_TESTS}" >> $GITHUB_OUTPUT
echo "ignored=${IGNORED_TESTS}" >> $GITHUB_OUTPUT

- name: Slack Notification
if: github.ref_name == 'master'
uses: rtCamp/action-slack-notify@v2
env:
SLACK_CHANNEL: atala-qa-results
SLACK_COLOR: ${{ steps.analyze_test_results.outputs.conclusion }}
SLACK_MESSAGE: |
Total: ${{ steps.analyze_test_results.outputs.tests }}
Failed: ${{ steps.analyze_test_results.outputs.failures }}
Errors in tests: ${{ steps.analyze_test_results.outputs.errors }}
Skipped (known bugs): ${{ steps.analyze_test_results.outputs.skipped }}
SLACK_TITLE: "Atala PRISM V2 E2E tests: ${{ steps.analyze_test_results.outputs.conclusion }}"
SLACK_USERNAME: circleci
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
2 changes: 1 addition & 1 deletion infrastructure/local/.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
MERCURY_MEDIATOR_VERSION=0.2.0
IRIS_SERVICE_VERSION=0.1.0
PRISM_AGENT_VERSION=0.16.1
PRISM_AGENT_VERSION=0.17.0
PORT=80
9 changes: 7 additions & 2 deletions tests/e2e-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@ dependencies {
testImplementation("junit:junit:4.13.2")
implementation("net.serenity-bdd:serenity-core:3.4.3")
implementation("net.serenity-bdd:serenity-cucumber:3.4.3")
implementation("net.serenity-bdd:serenity-single-page-report:3.4.3")
implementation("net.serenity-bdd:serenity-json-summary-report:3.4.3")
implementation("net.serenity-bdd:serenity-screenplay-rest:3.4.3")
// Beautify exceptions handling assertions
testImplementation("org.assertj:assertj-core:3.23.1")
// Navigate through Json with xpath
testImplementation("com.jayway.jsonpath:json-path:2.7.0")
}

buildscript {
dependencies {
classpath("net.serenity-bdd:serenity-single-page-report:3.4.3")
classpath("net.serenity-bdd:serenity-json-summary-report:3.4.3")
}
}

/**
* Add HTML one-pager and JSON summary report to be produced
*/
Expand Down
14 changes: 14 additions & 0 deletions tests/e2e-tests/src/main/kotlin/api_models/CredentialSchema.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package api_models

data class CredentialSchema(
var id: String? = null,
var name: String? = null,
var version: String? = null,
var description: String? = null,
var author: String? = null,
var authored: String? = null,
var kind: String? = null,
var self: String? = null,
var attributes: List<String>? = listOf(""),
var tags: List<String>? = listOf("")
)
14 changes: 14 additions & 0 deletions tests/e2e-tests/src/test/kotlin/common/CredentialSchemas.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package common

import api_models.CredentialSchema

object CredentialSchemas {
val STUDENT_SCHEMA = CredentialSchema(
author = "University",
name = "Student schema",
description = "Simple student credentials schema",
attributes = listOf("name", "age"),
tags = listOf("school", "students"),
version = "1.0"
)
}
8 changes: 8 additions & 0 deletions tests/e2e-tests/src/test/kotlin/common/TestConstants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package common

import java.util.UUID

object TestConstants {
val CREDENTIAL_SCHEMAS = CredentialSchemas
val RANDOM_CONSTAND_UUID = UUID.randomUUID().toString()
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import common.Utils.lastResponseObject
import common.Utils.wait
import io.cucumber.java.en.Then
import io.cucumber.java.en.When
import net.serenitybdd.screenplay.Actor
import net.serenitybdd.screenplay.rest.interactions.Get
import net.serenitybdd.screenplay.rest.interactions.Post
import net.serenitybdd.screenplay.rest.questions.ResponseConsequence
Expand All @@ -16,19 +17,18 @@ import org.hamcrest.CoreMatchers.*

class ConnectionSteps {

@When("Acme generates a connection invitation")
fun inviterGeneratesAConnectionInvitation() {
@When("{actor} generates a connection invitation")
fun inviterGeneratesAConnectionInvitation(inviter: Actor) {
// Acme(Issuer) initiates a connection
// and sends it to Bob(Holder) out-of-band, e.g. using QR-code
val connectionLabel = "Connection with ${Bob.name}"
Acme.attemptsTo(
val connectionLabel = "New Connection"
inviter.attemptsTo(
Post.to("/connections")
.with {
it.header("Content-Type", "application/json")
it.body("""{"label": "$connectionLabel"}""")
}
)
Acme.should(
inviter.should(
ResponseConsequence.seeThatResponse("Generates connection request") { response ->
response.statusCode(201)
response.body("connectionId", notNullValue())
Expand All @@ -39,45 +39,44 @@ class ConnectionSteps {
}
)
// Acme remembers invitation URL to send it out of band to Bob
Acme.remember(
inviter.remember(
"invitationUrl",
lastResponseObject("", Connection::class)
.invitation.invitationUrl.split("=")[1]
)
Acme.remember(
inviter.remember(
"invitation",
lastResponseObject("invitation", Invitation::class)
)

// Acme remembers its connection ID for further use
Acme.remember(
inviter.remember(
"connectionId",
lastResponseObject("", Connection::class)
.connectionId
)
}

@When("Bob receives the connection invitation")
fun inviteeReceivesTheConnectionInvitation() {
@When("{actor} receives the connection invitation from {actor}")
fun inviteeReceivesTheConnectionInvitation(invitee: Actor, inviter: Actor) {
// Here out of band transfer of connection QR code is happening
// and Bob (Holder) gets an invitation URL
// they're accepting connection invitation by POST request specifying achieved invitation
// we demonstrate it by Bob remembering invitationUrl that Acme recalls
Bob.remember("invitationUrl", Acme.recall<String>("invitationUrl"))
invitee.remember("invitationUrl", inviter.recall<String>("invitationUrl"))
}

@When("Bob sends a connection request to Acme")
fun inviteeSendsAConnectionRequestToInviter() {
@When("{actor} sends a connection request to {actor}")
fun inviteeSendsAConnectionRequestToInviter(invitee: Actor, inviter: Actor) {
// Bob accepts connection using achieved out-of-band invitation
Bob.attemptsTo(
invitee.attemptsTo(
Post.to("/connection-invitations")
.with {
it.header("Content-Type", "application/json")
it.body("""{"invitation": "${Bob.recall<String>("invitationUrl")}"}""")
it.body("""{"invitation": "${invitee.recall<String>("invitationUrl")}"}""")
}
)
val acmeInvitation = Acme.recall<Invitation>("invitation")
Bob.should(
val acmeInvitation = inviter.recall<Invitation>("invitation")
invitee.should(
ResponseConsequence.seeThatResponse("Accepts connection request") { response ->
response.statusCode(200)
response.body("connectionId", notNullValue())
Expand All @@ -91,18 +90,18 @@ class ConnectionSteps {
response.body("state", containsString("ConnectionRequestPending"))
}
)
Bob.remember("connectionId", lastResponseObject("", Connection::class).connectionId)
invitee.remember("connectionId", lastResponseObject("", Connection::class).connectionId)
}

@When("Acme receives the connection request")
fun inviterReceivesTheConnectionRequest() {
@When("{actor} receives the connection request")
fun inviterReceivesTheConnectionRequest(inviter: Actor) {
wait(
{
Acme.attemptsTo(
Get.resource("/connections/${Acme.recall<String>("connectionId")}"),
inviter.attemptsTo(
Get.resource("/connections/${inviter.recall<String>("connectionId")}"),
)
Acme.should(
ResponseConsequence.seeThatResponse("Get connection ${Acme.recall<String>("connectionId")}") {
inviter.should(
ResponseConsequence.seeThatResponse("Get connection ${inviter.recall<String>("connectionId")}") {
it.statusCode(200)
}
)
Expand All @@ -112,17 +111,17 @@ class ConnectionSteps {
)
}

@When("Acme sends a connection response to Bob")
fun inviterSendsAConnectionResponseToInvitee() {
@When("{actor} sends a connection response to {actor}")
fun inviterSendsAConnectionResponseToInvitee(inviter: Actor, invitee: Actor) {
// Acme(Issuer) checks their connections to check if invitation was accepted by Bob(Holder)
// and sends final connection response
wait(
{
Acme.attemptsTo(
Get.resource("/connections/${Acme.recall<String>("connectionId")}"),
inviter.attemptsTo(
Get.resource("/connections/${inviter.recall<String>("connectionId")}"),
)
Acme.should(
ResponseConsequence.seeThatResponse("Get connection ${Acme.recall<String>("connectionId")}") {
inviter.should(
ResponseConsequence.seeThatResponse("Get connection ${inviter.recall<String>("connectionId")}") {
it.statusCode(200)
}
)
Expand All @@ -132,16 +131,16 @@ class ConnectionSteps {
)
}

@When("Bob receives the connection response")
fun inviteeReceivesTheConnectionResponse() {
@When("{actor} receives the connection response")
fun inviteeReceivesTheConnectionResponse(invitee: Actor) {
// Bob (Holder) receives final connection response
wait(
{
Bob.attemptsTo(
Get.resource("/connections/${Bob.recall<String>("connectionId")}")
invitee.attemptsTo(
Get.resource("/connections/${invitee.recall<String>("connectionId")}")
)
Bob.should(
ResponseConsequence.seeThatResponse("Get connection ${Bob.recall<String>("connectionId")}") {
invitee.should(
ResponseConsequence.seeThatResponse("Get connection ${invitee.recall<String>("connectionId")}") {
it.statusCode(200)
}
)
Expand All @@ -151,36 +150,36 @@ class ConnectionSteps {
)
}

@Then("Acme and Bob have a connection")
fun inviterAndInviteeHaveAConnection() {
@Then("{actor} and {actor} have a connection")
fun inviterAndInviteeHaveAConnection(inviter: Actor, invitee: Actor) {
// Connection established. Both parties exchanged their DIDs with each other
Acme.attemptsTo(
Get.resource("/connections/${Acme.recall<String>("connectionId")}"),
inviter.attemptsTo(
Get.resource("/connections/${inviter.recall<String>("connectionId")}"),
)
Acme.should(
ResponseConsequence.seeThatResponse("Get connection ${Acme.recall<String>("connectionId")}") {
inviter.should(
ResponseConsequence.seeThatResponse("Get connection ${inviter.recall<String>("connectionId")}") {
it.statusCode(200)
}
)
Acme.remember("connection", lastResponseObject("", Connection::class))
inviter.remember("connection", lastResponseObject("", Connection::class))

Bob.attemptsTo(
Get.resource("/connections/${Bob.recall<String>("connectionId")}")
invitee.attemptsTo(
Get.resource("/connections/${invitee.recall<String>("connectionId")}")
)
Bob.should(
ResponseConsequence.seeThatResponse("Get connection ${Bob.recall<String>("connectionId")}") {
invitee.should(
ResponseConsequence.seeThatResponse("Get connection ${invitee.recall<String>("connectionId")}") {
it.statusCode(200)
}
)
Bob.remember("connection", lastResponseObject("", Connection::class))
invitee.remember("connection", lastResponseObject("", Connection::class))

assertThat(Acme.recall<Connection>("connection").myDid)
.isEqualTo(Bob.recall<Connection>("connection").theirDid)
assertThat(Acme.recall<Connection>("connection").theirDid)
.isEqualTo(Bob.recall<Connection>("connection").myDid)
assertThat(Acme.recall<Connection>("connection").state)
assertThat(inviter.recall<Connection>("connection").myDid)
.isEqualTo(invitee.recall<Connection>("connection").theirDid)
assertThat(inviter.recall<Connection>("connection").theirDid)
.isEqualTo(invitee.recall<Connection>("connection").myDid)
assertThat(inviter.recall<Connection>("connection").state)
.isEqualTo("ConnectionResponseSent")
assertThat(Bob.recall<Connection>("connection").state)
assertThat(invitee.recall<Connection>("connection").state)
.isEqualTo("ConnectionResponseReceived")
}
}
Loading