From 29e50ab9e70242381e59d7a82772f12eaca3bd2f Mon Sep 17 00:00:00 2001 From: toktar Date: Thu, 7 Jul 2022 18:42:46 +0300 Subject: [PATCH 1/9] fix: Fix headers contentType behavior --- cmd/serve.go | 6 ++---- services/request_service.go | 7 ++++++- services/request_service_test.go | 4 +++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cmd/serve.go b/cmd/serve.go index 36838f47..effdff3b 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -72,11 +72,9 @@ func serve() { accept := strings.Split(c.Request().Header.Get(echo.HeaderAccept), ";")[0] log.Trace().Msgf("Accept: %s", accept) - var requestedContentType types.ContentType - if strings.Contains(accept, string(types.JSONLD)) { + requestedContentType := types.ContentType(accept) + if accept == "*/*" { requestedContentType = types.DIDJSONLD - } else { - requestedContentType = types.DIDJSON } log.Debug().Msgf("Requested content type: %s", requestedContentType) diff --git a/services/request_service.go b/services/request_service.go index b631cc54..3bad9e91 100644 --- a/services/request_service.go +++ b/services/request_service.go @@ -4,6 +4,7 @@ import ( // jsonpb Marshaller is deprecated, but is needed because there's only one way to proto // marshal in combination with our proto generator version "encoding/json" + "fmt" "github.com/rs/zerolog/log" @@ -123,8 +124,12 @@ func (rs RequestService) Resolve(did string, resolutionOptions types.ResolutionO return types.DidResolution{ResolutionMetadata: didResolutionMetadata}, nil } - if didResolutionMetadata.ContentType == types.DIDJSONLD { + if didResolutionMetadata.ContentType == types.DIDJSONLD || didResolutionMetadata.ContentType == types.JSONLD { didDoc.Context = append(didDoc.Context, types.DIDSchemaJSONLD) + } else if didResolutionMetadata.ContentType == types.DIDJSON { + didDoc.Context = []string{} + } else { + return types.DidResolution{}, fmt.Errorf("content type %s is not supported", didResolutionMetadata.ContentType) } return types.DidResolution{Did: didDoc, Metadata: metadata, ResolutionMetadata: didResolutionMetadata}, nil } diff --git a/services/request_service_test.go b/services/request_service_test.go index 2e0f2826..34702fb1 100644 --- a/services/request_service_test.go +++ b/services/request_service_test.go @@ -102,8 +102,10 @@ func TestResolve(t *testing.T) { MethodSpecificId: subtest.identifier, Method: subtest.method, } - if (subtest.resolutionType == types.DIDJSONLD || subtest.resolutionType == types.JSONLD) && subtest.expectedError == "" { + if (subtest.resolutionType == "" || subtest.resolutionType == types.DIDJSONLD) && subtest.expectedError == "" { subtest.expectedDID.Context = []string{types.DIDSchemaJSONLD} + } else { + subtest.expectedDID.Context = nil } resolutionResult, err := requestService.Resolve(id, types.ResolutionOption{Accept: subtest.resolutionType}) From 01774a4b482d275fb213046bd0c0eac1e22ffebe Mon Sep 17 00:00:00 2001 From: toktar Date: Fri, 8 Jul 2022 15:46:08 +0300 Subject: [PATCH 2/9] test: Add integration test --- tests/e2e-pytest/helpers.py | 44 +++++++++++++++++++++++++++++++ tests/e2e-pytest/requirements.txt | 3 +++ tests/e2e-pytest/test_cosmos.py | 31 ++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 tests/e2e-pytest/helpers.py create mode 100644 tests/e2e-pytest/requirements.txt create mode 100644 tests/e2e-pytest/test_cosmos.py diff --git a/tests/e2e-pytest/helpers.py b/tests/e2e-pytest/helpers.py new file mode 100644 index 00000000..1da5e65e --- /dev/null +++ b/tests/e2e-pytest/helpers.py @@ -0,0 +1,44 @@ +import copy +import sys +import pexpect +import json + + +RESOLVER_URL = "http://localhost:1313" +PATH = "/1.0/identifiers/" + +TESTNET_DID = "did:cheqd:testnet:zFWM1mKVGGU2gHYuLAQcTJfZBebqBpGf" +TESTNET_FRAGMENT = TESTNET_DID + "#key1" +FAKE_TESTNET_DID = "did:cheqd:testnet:zF7rhDBfUt9d1gJPjx7s1JXfUY7oVWkY" +FAKE_TESTNET_FRAGMENT = TESTNET_DID + "#fake_key" + +MAINNET_DID = "did:cheqd:mainnet:zF7rhDBfUt9d1gJPjx7s1JXfUY7oVWkY" +MAINNET_FRAGMENT = MAINNET_DID + "#key1" +FAKE_MAINNET_DID = "did:cheqd:mainnet:zFWM1mKVGGU2gHYuLAQcTJfZBebqBpGf" +FAKE_MAINNET_FRAGMENT = MAINNET_DID + "#fake_key" + + +IMPLICIT_TIMEOUT = 40 +ENCODING = "utf-8" +READ_BUFFER = 60000 + + +def run(command, params, expected_output): + cli = pexpect.spawn(f"{command} {params}", encoding=ENCODING, timeout=IMPLICIT_TIMEOUT, maxread=READ_BUFFER) + cli.logfile = sys.stdout + cli.expect(expected_output) + return cli + + +def run_interaction(cli, input_string, expected_output): + cli.sendline(input_string) + cli.expect(expected_output) + + +def json_loads(s_to_load: str) -> dict: + s = copy.copy(s_to_load) + s = s.replace("\\", "") + s = s.replace("\"[", "[") + s = s.replace("]\"", "]") + return json.loads(s) + diff --git a/tests/e2e-pytest/requirements.txt b/tests/e2e-pytest/requirements.txt new file mode 100644 index 00000000..3734659f --- /dev/null +++ b/tests/e2e-pytest/requirements.txt @@ -0,0 +1,3 @@ +pytest==6.0.2 +pytest-asyncio==0.16.0 +pexpect==4.8.0 \ No newline at end of file diff --git a/tests/e2e-pytest/test_cosmos.py b/tests/e2e-pytest/test_cosmos.py new file mode 100644 index 00000000..17c729ec --- /dev/null +++ b/tests/e2e-pytest/test_cosmos.py @@ -0,0 +1,31 @@ +import pytest +from helpers import run, TESTNET_DID, MAINNET_DID, TESTNET_FRAGMENT, MAINNET_FRAGMENT, \ + FAKE_TESTNET_DID, FAKE_MAINNET_DID, FAKE_TESTNET_FRAGMENT, FAKE_MAINNET_FRAGMENT, RESOLVER_URL, PATH + + +@pytest.mark.parametrize( + "did_url, expected_output", + [ + (TESTNET_DID, fr"didDocument(.*?)\"id\":\"{TESTNET_DID}\"(.*?)didDocumentMetadata" + r"(.*?)didResolutionMetadata"), + (MAINNET_DID, fr"didDocument(.*?)\"id\":\"{MAINNET_DID}\"(.*?)didDocumentMetadata" + r"(.*?)didResolutionMetadata"), + (FAKE_TESTNET_DID, r"didDocument\":null,\"didDocumentMetadata\":\[\]," + r"\"didResolutionMetadata(.*?)\"error\":\"notFound\""), + (FAKE_MAINNET_DID, r"didDocument\":null,\"didDocumentMetadata\":\[\]," + r"\"didResolutionMetadata(.*?)\"error\":\"notFound\""), + ("did:wrong_method:MTMxDQKMTMxDQKMT", r"didDocument\":null,\"didDocumentMetadata\":\[\]," + r"\"didResolutionMetadata(.*?)\"error\":\"methodNotSupported\""), + + (TESTNET_FRAGMENT, fr"\"contentStream\":(.*?)\"id\":\"{TESTNET_FRAGMENT}\"(.*?)contentMetadata" + r"(.*?)dereferencingMetadata\""), + (MAINNET_FRAGMENT, fr"\"contentStream\":(.*?)\"id\":\"{MAINNET_FRAGMENT}\"(.*?)contentMetadata" + r"(.*?)dereferencingMetadata\""), + (FAKE_TESTNET_FRAGMENT, r"\"contentStream\":null,\"contentMetadata\":\[\]," + r"\"dereferencingMetadata(.*?)\"error\":\"FragmentNotFound\""), + (FAKE_MAINNET_FRAGMENT, r"\"contentStream\":null,\"contentMetadata\":\[\]," + r"\"dereferencingMetadata(.*?)\"error\":\"FragmentNotFound\""), + ] +) +def test_resolution(did_url, expected_output): + run("curl", RESOLVER_URL + PATH + did_url.replace("#", "%23"), expected_output) From 75dba014dbff802f8f5ab1a5a5068a09bf0bc868 Mon Sep 17 00:00:00 2001 From: toktar Date: Fri, 8 Jul 2022 15:48:12 +0300 Subject: [PATCH 3/9] test: Update pipelines --- .github/workflows/test.yml | 33 +++++++++++++++++++ .../{test_cosmos.py => test_resolution.py} | 0 2 files changed, 33 insertions(+) rename tests/e2e-pytest/{test_cosmos.py => test_resolution.py} (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7fb1e613..0ba2c07c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,3 +16,36 @@ jobs: - name: Run Golang unit tests run: go test -v ./... + + python-integration-tests: + name: "Python integration tests" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Download node Docker image + uses: actions/download-artifact@v3 + with: + name: cheqd-did-resolver.tar + + - name: Load node Docker image + run: docker load -i cheqd-did-resolver.tar + + - name: Setup full resolver Docker + working-directory: ./docker + run: | + docker compose --profile full up + + - name: Setup Python environment + working-directory: ./tests/e2e-pytest + run: | + set -euo pipefail + pip3 install -r requirements.txt >> /dev/null + sudo chmod -R 775 /home/runner/ + + - name: Run tests + working-directory: ./tests/e2e-pytest + run: | + set -euo pipefail + pytest -v -rP *.py \ No newline at end of file diff --git a/tests/e2e-pytest/test_cosmos.py b/tests/e2e-pytest/test_resolution.py similarity index 100% rename from tests/e2e-pytest/test_cosmos.py rename to tests/e2e-pytest/test_resolution.py From 2cef37e2c676e194545fe6a462aa541a3fdd6d8f Mon Sep 17 00:00:00 2001 From: toktar Date: Fri, 8 Jul 2022 15:56:11 +0300 Subject: [PATCH 4/9] Update tests dir name --- .github/workflows/test.yml | 4 ++-- tests/{e2e-pytest => pytest}/helpers.py | 0 tests/{e2e-pytest => pytest}/requirements.txt | 0 tests/{e2e-pytest => pytest}/test_resolution.py | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename tests/{e2e-pytest => pytest}/helpers.py (100%) rename tests/{e2e-pytest => pytest}/requirements.txt (100%) rename tests/{e2e-pytest => pytest}/test_resolution.py (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0ba2c07c..1e7f0cde 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,14 +38,14 @@ jobs: docker compose --profile full up - name: Setup Python environment - working-directory: ./tests/e2e-pytest + working-directory: ./tests/pytest run: | set -euo pipefail pip3 install -r requirements.txt >> /dev/null sudo chmod -R 775 /home/runner/ - name: Run tests - working-directory: ./tests/e2e-pytest + working-directory: ./tests/pytest run: | set -euo pipefail pytest -v -rP *.py \ No newline at end of file diff --git a/tests/e2e-pytest/helpers.py b/tests/pytest/helpers.py similarity index 100% rename from tests/e2e-pytest/helpers.py rename to tests/pytest/helpers.py diff --git a/tests/e2e-pytest/requirements.txt b/tests/pytest/requirements.txt similarity index 100% rename from tests/e2e-pytest/requirements.txt rename to tests/pytest/requirements.txt diff --git a/tests/e2e-pytest/test_resolution.py b/tests/pytest/test_resolution.py similarity index 100% rename from tests/e2e-pytest/test_resolution.py rename to tests/pytest/test_resolution.py From 3481a3ef0da02b0c6d9310138348c29b6870178a Mon Sep 17 00:00:00 2001 From: toktar Date: Fri, 8 Jul 2022 16:05:20 +0300 Subject: [PATCH 5/9] Update GA test --- .github/workflows/test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1e7f0cde..ac94fb65 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,8 +34,7 @@ jobs: - name: Setup full resolver Docker working-directory: ./docker - run: | - docker compose --profile full up + run: docker compose --profile full up - name: Setup Python environment working-directory: ./tests/pytest @@ -48,4 +47,4 @@ jobs: working-directory: ./tests/pytest run: | set -euo pipefail - pytest -v -rP *.py \ No newline at end of file + pytest -v -rP ./*.py \ No newline at end of file From 31a8bdc30ce48dc5b8ba110b9dffb0d0bf4c9658 Mon Sep 17 00:00:00 2001 From: toktar Date: Fri, 8 Jul 2022 16:12:44 +0300 Subject: [PATCH 6/9] Update GA test --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ac94fb65..e81fc116 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: - name: Setup full resolver Docker working-directory: ./docker - run: docker compose --profile full up + run: docker compose --profile full up -d - name: Setup Python environment working-directory: ./tests/pytest From 76e91cb21da1e0f2b8fe4be6247604c1eba9f41e Mon Sep 17 00:00:00 2001 From: toktar Date: Fri, 8 Jul 2022 17:27:45 +0300 Subject: [PATCH 7/9] Add html support --- cmd/serve.go | 2 ++ services/request_service.go | 17 ++++++++++++++--- types/constants.go | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cmd/serve.go b/cmd/serve.go index effdff3b..44afbfa8 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -75,6 +75,8 @@ func serve() { requestedContentType := types.ContentType(accept) if accept == "*/*" { requestedContentType = types.DIDJSONLD + } else if strings.Contains(accept, string(types.HTML)) { + requestedContentType = types.HTML } log.Debug().Msgf("Requested content type: %s", requestedContentType) diff --git a/services/request_service.go b/services/request_service.go index 3bad9e91..40d2abf5 100644 --- a/services/request_service.go +++ b/services/request_service.go @@ -32,13 +32,23 @@ func (rs RequestService) IsDidUrl(didUrl string) bool { } func (rs RequestService) ProcessDIDRequest(didUrl string, resolutionOptions types.ResolutionOption) (string, error) { + var result string + var err error if rs.IsDidUrl(didUrl) { log.Trace().Msgf("Dereferencing %s", didUrl) - return rs.prepareDereferencingResult(didUrl, types.DereferencingOption(resolutionOptions)) + result, err = rs.prepareDereferencingResult(didUrl, types.DereferencingOption(resolutionOptions)) } else { log.Trace().Msgf("Resolving %s", didUrl) - return rs.prepareResolutionResult(didUrl, resolutionOptions) + result, err = rs.prepareResolutionResult(didUrl, resolutionOptions) } + + if resolutionOptions.Accept == types.HTML { + return "

Cheqd DID Resolver

", err
+	}
+	return result, err
+
 }
 
 func (rs RequestService) prepareResolutionResult(did string, resolutionOptions types.ResolutionOption) (string, error) {
@@ -126,11 +136,12 @@ func (rs RequestService) Resolve(did string, resolutionOptions types.ResolutionO
 
 	if didResolutionMetadata.ContentType == types.DIDJSONLD || didResolutionMetadata.ContentType == types.JSONLD {
 		didDoc.Context = append(didDoc.Context, types.DIDSchemaJSONLD)
-	} else if didResolutionMetadata.ContentType == types.DIDJSON {
+	} else if didResolutionMetadata.ContentType == types.DIDJSON || didResolutionMetadata.ContentType == types.HTML {
 		didDoc.Context = []string{}
 	} else {
 		return types.DidResolution{}, fmt.Errorf("content type %s is not supported", didResolutionMetadata.ContentType)
 	}
+
 	return types.DidResolution{Did: didDoc, Metadata: metadata, ResolutionMetadata: didResolutionMetadata}, nil
 }
 
diff --git a/types/constants.go b/types/constants.go
index 4d552470..6ccbf4c2 100644
--- a/types/constants.go
+++ b/types/constants.go
@@ -20,6 +20,7 @@ const (
 	DIDJSON   ContentType = "application/did+json"
 	DIDJSONLD ContentType = "application/did+ld+json"
 	JSONLD    ContentType = "application/ld+json"
+	HTML    ContentType = "text/html"
 )
 
 const (

From 4c5982719bf12054632b21919198610a113bb57e Mon Sep 17 00:00:00 2001
From: toktar 
Date: Fri, 8 Jul 2022 17:45:13 +0300
Subject: [PATCH 8/9] Make lint happy

---
 services/request_service.go | 7 +++----
 types/constants.go          | 2 +-
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/services/request_service.go b/services/request_service.go
index 40d2abf5..b1ffa251 100644
--- a/services/request_service.go
+++ b/services/request_service.go
@@ -43,12 +43,11 @@ func (rs RequestService) ProcessDIDRequest(didUrl string, resolutionOptions type
 	}
 
 	if resolutionOptions.Accept == types.HTML {
-		return "

Cheqd DID Resolver

", err
+		return "

Cheqd DID Resolver

", err
 	}
 	return result, err
-
 }
 
 func (rs RequestService) prepareResolutionResult(did string, resolutionOptions types.ResolutionOption) (string, error) {
diff --git a/types/constants.go b/types/constants.go
index 6ccbf4c2..9fb38eb5 100644
--- a/types/constants.go
+++ b/types/constants.go
@@ -20,7 +20,7 @@ const (
 	DIDJSON   ContentType = "application/did+json"
 	DIDJSONLD ContentType = "application/did+ld+json"
 	JSONLD    ContentType = "application/ld+json"
-	HTML    ContentType = "text/html"
+	HTML      ContentType = "text/html"
 )
 
 const (

From 645b062551b6485821414a94ed5d67cf1ac0ae36 Mon Sep 17 00:00:00 2001
From: toktar 
Date: Mon, 11 Jul 2022 14:57:36 +0300
Subject: [PATCH 9/9] Add integration tests for headers

---
 tests/pytest/helpers.py         | 10 +++++-----
 tests/pytest/test_resolution.py | 32 +++++++++++++++++++++++++++++++-
 2 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/tests/pytest/helpers.py b/tests/pytest/helpers.py
index 1da5e65e..885e2cb1 100644
--- a/tests/pytest/helpers.py
+++ b/tests/pytest/helpers.py
@@ -17,6 +17,11 @@
 FAKE_MAINNET_DID = "did:cheqd:mainnet:zFWM1mKVGGU2gHYuLAQcTJfZBebqBpGf"
 FAKE_MAINNET_FRAGMENT = MAINNET_DID + "#fake_key"
 
+DIDJSON = "application/did+json"
+DIDLDJSON = "application/did+ld+json"
+LDJSON = "application/ld+json"
+HTML = "text/html"
+
 
 IMPLICIT_TIMEOUT = 40
 ENCODING = "utf-8"
@@ -30,11 +35,6 @@ def run(command, params, expected_output):
     return cli
 
 
-def run_interaction(cli, input_string, expected_output):
-    cli.sendline(input_string)
-    cli.expect(expected_output)
-
-
 def json_loads(s_to_load: str) -> dict:
     s = copy.copy(s_to_load)
     s = s.replace("\\", "")
diff --git a/tests/pytest/test_resolution.py b/tests/pytest/test_resolution.py
index 17c729ec..2a845be4 100644
--- a/tests/pytest/test_resolution.py
+++ b/tests/pytest/test_resolution.py
@@ -1,6 +1,11 @@
+import re
+
 import pytest
+import requests
+
 from helpers import run, TESTNET_DID, MAINNET_DID, TESTNET_FRAGMENT, MAINNET_FRAGMENT, \
-    FAKE_TESTNET_DID, FAKE_MAINNET_DID, FAKE_TESTNET_FRAGMENT, FAKE_MAINNET_FRAGMENT, RESOLVER_URL, PATH
+    FAKE_TESTNET_DID, FAKE_MAINNET_DID, FAKE_TESTNET_FRAGMENT, FAKE_MAINNET_FRAGMENT, RESOLVER_URL, PATH, \
+    LDJSON, DIDJSON, DIDLDJSON, HTML
 
 
 @pytest.mark.parametrize(
@@ -29,3 +34,28 @@
 )
 def test_resolution(did_url, expected_output):
     run("curl", RESOLVER_URL + PATH + did_url.replace("#", "%23"), expected_output)
+
+
+@pytest.mark.parametrize(
+    "accept, expected_header, expected_body",
+    [
+        (LDJSON, LDJSON, r"(.*?)didDocument(.*?)@context(.*?)didDocumentMetadata"
+                         r"(.*?)didResolutionMetadata(.*?)application/ld\+json"),
+        (DIDLDJSON, DIDLDJSON, "(.*?)didDocument(.*?)@context(.*?)didDocumentMetadata"
+                               "(.*?)didResolutionMetadata(.*?)application/did\+ld\+json"),
+        ("", DIDLDJSON, "(.*?)didDocument(.*?)@context(.*?)didDocumentMetadata"
+                        "(.*?)didResolutionMetadata(.*?)application/did\+ld\+json"),
+        (DIDJSON, DIDJSON, r"(.*?)didDocument(.*?)(?!`@context`)(.*?)didDocumentMetadata"
+                           r"(.*?)didResolutionMetadata(.*?)application/did\+json"),
+        (HTML + ",application/xhtml+xml", HTML, fr"(.*?)didDocument(.*?)(?!`@context`)(.*?)didDocumentMetadata"
+                                                fr"(.*?)didResolutionMetadata(.*?){HTML}"),
+    ]
+)
+def test_resolution_content_type(accept, expected_header, expected_body):
+    url = RESOLVER_URL + PATH + TESTNET_DID
+    header = {"Accept": accept} if accept else {}
+
+    r = requests.get(url, headers=header)
+
+    assert r.headers["Content-Type"] == expected_header
+    assert re.match(expected_body, r.text)