github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/cm/credentialmanifest_test.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package cm_test
     8  
     9  import (
    10  	_ "embed"
    11  	"encoding/json"
    12  	"testing"
    13  
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/hyperledger/aries-framework-go/pkg/doc/cm"
    17  	"github.com/hyperledger/aries-framework-go/pkg/doc/ld"
    18  	"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
    19  	"github.com/hyperledger/aries-framework-go/pkg/internal/ldtestutil"
    20  )
    21  
    22  // Sample Credential Manifests for a university degree.
    23  var (
    24  	//go:embed testdata/credential_manifest_university_degree.json
    25  	credentialManifestUniversityDegree []byte //nolint:gochecknoglobals
    26  	//go:embed testdata/credential_manifest_university_degree_with_format.json
    27  	credentialManifestUniversityDegreeWithFormat []byte //nolint:gochecknoglobals
    28  	//go:embed testdata/credential_manifest_university_degree_with_presentation_definition.json
    29  	credentialManifestUniversityDegreeWithPresentationDefinition []byte //nolint:gochecknoglobals
    30  )
    31  
    32  // Sample Credential Manifests for a driver's license.
    33  var (
    34  	//go:embed testdata/credential_manifest_drivers_license.json
    35  	credentialManifestDriversLicense []byte //nolint:gochecknoglobals
    36  	//go:embed testdata/credential_manifest_drivers_license_with_presentation_definition.json
    37  	credentialManifestDriversLicenseWithPresentationDefinition []byte //nolint:gochecknoglobals
    38  	//go:embed testdata/credential_manifest_drivers_license_with_presentation_definition_and_format.json
    39  	credentialManifestDriversLicenseWithPresentationDefinitionAndFormat []byte //nolint:gochecknoglobals
    40  	//go:embed testdata/credential_manifest_drivers_license_with_no_display_or_styles.json
    41  	credentialManifestDriversLicenseWithNoDisplayOrStyles []byte //nolint:gochecknoglobals
    42  )
    43  
    44  // Sample verifiable credential for a university degree.
    45  var (
    46  	//go:embed testdata/credential_university_degree.jsonld
    47  	validVC []byte // nolint:gochecknoglobals
    48  	//go:embed testdata/credential_university_degree_jwt.txt
    49  	validJWTVC []byte // nolint:gochecknoglobals
    50  )
    51  
    52  // miscellaneous samples.
    53  var (
    54  	//go:embed testdata/credential_manifest_multiple_vcs.json
    55  	credentialManifestMultipleVCs []byte // nolint:gochecknoglobals
    56  )
    57  
    58  const invalidJSONPath = "%InvalidJSONPath"
    59  
    60  func TestCredentialManifest_Unmarshal(t *testing.T) {
    61  	t.Run("Valid Credential Manifest", func(t *testing.T) {
    62  		t.Run("Without format or Presentation Submission", func(t *testing.T) {
    63  			makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
    64  		})
    65  		t.Run("With format", func(t *testing.T) {
    66  			makeCredentialManifestFromBytes(t, credentialManifestUniversityDegreeWithFormat)
    67  		})
    68  		t.Run("With Presentation Submission", func(t *testing.T) {
    69  			makeCredentialManifestFromBytes(t, credentialManifestUniversityDegreeWithPresentationDefinition)
    70  		})
    71  		t.Run("Without output descriptor display", func(t *testing.T) {
    72  			makeCredentialManifestFromBytes(t, credentialManifestDriversLicenseWithNoDisplayOrStyles)
    73  		})
    74  		t.Run("Without issuer optional properties", func(t *testing.T) {
    75  			credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
    76  
    77  			credentialManifest.Issuer = cm.Issuer{ID: "valid ID"}
    78  
    79  			invalidCredentialManifest, err := json.Marshal(credentialManifest)
    80  			require.NoError(t, err)
    81  
    82  			err = json.Unmarshal(invalidCredentialManifest, &credentialManifest)
    83  			require.NoError(t, err)
    84  		})
    85  	})
    86  	t.Run("Invalid Credential Manifest", func(t *testing.T) {
    87  		t.Run("Missing ID", func(t *testing.T) {
    88  			credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
    89  
    90  			credentialManifest.ID = ""
    91  
    92  			invalidCredentialManifest, err := json.Marshal(credentialManifest)
    93  			require.NoError(t, err)
    94  
    95  			err = json.Unmarshal(invalidCredentialManifest, &credentialManifest)
    96  			require.EqualError(t, err, "invalid credential manifest: ID missing")
    97  		})
    98  		t.Run("Missing issuer", func(t *testing.T) {
    99  			credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   100  
   101  			credentialManifest.Issuer = cm.Issuer{}
   102  
   103  			invalidCredentialManifest, err := json.Marshal(credentialManifest)
   104  			require.NoError(t, err)
   105  
   106  			err = json.Unmarshal(invalidCredentialManifest, &credentialManifest)
   107  			require.EqualError(t, err, "invalid credential manifest: issuer ID missing")
   108  		})
   109  		t.Run("Missing issuer ID", func(t *testing.T) {
   110  			credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   111  
   112  			credentialManifest.Issuer.ID = ""
   113  
   114  			invalidCredentialManifest, err := json.Marshal(credentialManifest)
   115  			require.NoError(t, err)
   116  
   117  			err = json.Unmarshal(invalidCredentialManifest, &credentialManifest)
   118  			require.EqualError(t, err, "invalid credential manifest: issuer ID missing")
   119  		})
   120  		t.Run("No output descriptors", func(t *testing.T) {
   121  			credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   122  
   123  			credentialManifest.OutputDescriptors = nil
   124  
   125  			invalidCredentialManifest, err := json.Marshal(credentialManifest)
   126  			require.NoError(t, err)
   127  
   128  			err = json.Unmarshal(invalidCredentialManifest, &credentialManifest)
   129  			require.EqualError(t, err, "invalid credential manifest: no output descriptors found")
   130  		})
   131  		t.Run("Output descriptor missing ID", func(t *testing.T) {
   132  			credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   133  
   134  			credentialManifest.OutputDescriptors[0].ID = ""
   135  
   136  			invalidCredentialManifest, err := json.Marshal(credentialManifest)
   137  			require.NoError(t, err)
   138  
   139  			err = json.Unmarshal(invalidCredentialManifest, &credentialManifest)
   140  			require.EqualError(t, err, "invalid credential manifest: missing ID for output descriptor at index 0")
   141  		})
   142  		t.Run("Duplicate output descriptor IDs", func(t *testing.T) {
   143  			credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   144  
   145  			credentialManifest.OutputDescriptors =
   146  				append(credentialManifest.OutputDescriptors,
   147  					&cm.OutputDescriptor{ID: credentialManifest.OutputDescriptors[0].ID})
   148  
   149  			invalidCredentialManifest, err := json.Marshal(credentialManifest)
   150  			require.NoError(t, err)
   151  
   152  			err = json.Unmarshal(invalidCredentialManifest, &credentialManifest)
   153  			require.EqualError(t, err, "invalid credential manifest: the ID bachelors_degree appears "+
   154  				"in multiple output descriptors")
   155  		})
   156  		t.Run("Missing schema for output descriptor", func(t *testing.T) {
   157  			credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   158  
   159  			credentialManifest.OutputDescriptors[0].Schema = ""
   160  
   161  			invalidCredentialManifest, err := json.Marshal(credentialManifest)
   162  			require.NoError(t, err)
   163  
   164  			err = json.Unmarshal(invalidCredentialManifest, &credentialManifest)
   165  			require.EqualError(t, err, "invalid credential manifest: missing schema for "+
   166  				"output descriptor at index 0")
   167  		})
   168  		t.Run("Invalid JSONPath", func(t *testing.T) {
   169  			t.Run("Display title", func(t *testing.T) {
   170  				var credentialManifest cm.CredentialManifest
   171  
   172  				err := json.Unmarshal(createMarshalledCredentialManifestWithInvalidTitleJSONPath(t), &credentialManifest)
   173  				require.EqualError(t, err, "invalid credential manifest: display title for output descriptor "+
   174  					`at index 0 is invalid: path "%InvalidJSONPath" at index 0 is not a valid JSONPath: parsing error: `+
   175  					`%InvalidJSONPath	:1:1 - 1:2 unexpected "%" while scanning extensions`)
   176  			})
   177  			t.Run("Display subtitle", func(t *testing.T) {
   178  				var credentialManifest cm.CredentialManifest
   179  
   180  				err := json.Unmarshal(createMarshalledCredentialManifestWithInvalidSubtitleJSONPath(t), &credentialManifest)
   181  				require.EqualError(t, err, "invalid credential manifest: display subtitle for output descriptor "+
   182  					`at index 0 is invalid: path "%InvalidJSONPath" at index 0 is not a valid JSONPath: parsing error: `+
   183  					`%InvalidJSONPath	:1:1 - 1:2 unexpected "%" while scanning extensions`)
   184  			})
   185  			t.Run("Display description", func(t *testing.T) {
   186  				var credentialManifest cm.CredentialManifest
   187  
   188  				err := json.Unmarshal(createMarshalledCredentialManifestWithInvalidDescriptionJSONPath(t), &credentialManifest)
   189  				require.EqualError(t, err, "invalid credential manifest: display description for output "+
   190  					`descriptor at index 0 is invalid: path "%InvalidJSONPath" at index 0 is not a valid JSONPath: `+
   191  					`parsing error: %InvalidJSONPath	:1:1 - 1:2 unexpected "%" while scanning extensions`)
   192  			})
   193  			t.Run("Display property", func(t *testing.T) {
   194  				var credentialManifest cm.CredentialManifest
   195  
   196  				err := json.Unmarshal(createMarshalledCredentialManifestWithInvalidPropertyJSONPath(t), &credentialManifest)
   197  				require.EqualError(t, err, "invalid credential manifest: display property at index 0 for output "+
   198  					`descriptor at index 0 is invalid: path "%InvalidJSONPath" at index 0 is not a valid JSONPath: `+
   199  					`parsing error: %InvalidJSONPath	:1:1 - 1:2 unexpected "%" while scanning extensions`)
   200  			})
   201  		})
   202  		t.Run("Invalid schema type", func(t *testing.T) {
   203  			var credentialManifest cm.CredentialManifest
   204  
   205  			err := json.Unmarshal(createMarshalledCredentialManifestWithInvalidSchemaType(t), &credentialManifest)
   206  			require.EqualError(t, err, "invalid credential manifest: display title for output descriptor at "+
   207  				"index 0 is invalid: InvalidSchemaType is not a valid schema type")
   208  		})
   209  		t.Run("Invalid schema format", func(t *testing.T) {
   210  			var credentialManifest cm.CredentialManifest
   211  
   212  			err := json.Unmarshal(createMarshalledCredentialManifestWithInvalidSchemaFormat(t), &credentialManifest)
   213  			require.EqualError(t, err, "invalid credential manifest: display title for output descriptor at "+
   214  				"index 0 is invalid: UnknownFormat is not a valid string schema format")
   215  		})
   216  		t.Run("Missing paths and text", func(t *testing.T) {
   217  			var credentialManifest cm.CredentialManifest
   218  
   219  			err := json.Unmarshal(createMarshalledCredentialManifestWithMissingPropertyJSONPathAndText(t), &credentialManifest)
   220  			require.EqualError(t, err, "invalid credential manifest: display property at index 0 for output descriptor at "+
   221  				"index 0 is invalid: display mapping object must contain either a paths or a text property")
   222  		})
   223  		t.Run("Missing image URI", func(t *testing.T) {
   224  			credentialManifest := createCredentialManifestWithNoImageURI(t)
   225  
   226  			invalidCredentialManifest, err := json.Marshal(credentialManifest)
   227  			require.NoError(t, err)
   228  
   229  			err = json.Unmarshal(invalidCredentialManifest, &credentialManifest)
   230  			require.EqualError(t, err, "invalid credential manifest: uri missing for image at index 0")
   231  		})
   232  	})
   233  }
   234  
   235  func TestResolveResponse(t *testing.T) {
   236  	type match struct {
   237  		Title       string
   238  		Subtitle    string
   239  		Description string
   240  		Properties  map[string]*cm.ResolvedProperty
   241  	}
   242  
   243  	// nolint:lll
   244  	t.Run("Successes", func(t *testing.T) {
   245  		testTable := map[string]struct {
   246  			manifest []byte
   247  			response []byte
   248  			expected map[string]*match
   249  		}{
   250  			"single descriptor and credential": {
   251  				manifest: credentialManifestDriversLicense,
   252  				response: vpWithDriversLicenseVCAndCredentialResponse,
   253  				expected: map[string]*match{
   254  					"driver_license_output": {
   255  						Title:    "Washington State Driver License",
   256  						Subtitle: "Class A, Commercial",
   257  						Description: "License to operate a vehicle with a gross combined weight " +
   258  							"rating (GCWR) of 26,001 or more pounds, as long as the GVWR of the vehicle(s) " +
   259  							"being towed is over 10,000 pounds.",
   260  						Properties: map[string]*cm.ResolvedProperty{
   261  							"Driving License Number": {
   262  								Label: "Driving License Number",
   263  								Value: "34DGE352",
   264  								Schema: cm.Schema{
   265  									Type: "boolean",
   266  								},
   267  							},
   268  						},
   269  					},
   270  				},
   271  			},
   272  			"multiple descriptor and credentials": {
   273  				manifest: credentialManifestMultipleVCs,
   274  				response: vpMultipleWithCredentialResponse,
   275  				expected: map[string]*match{
   276  					"prc_output": {
   277  						Title:       "Permanent Resident Card",
   278  						Subtitle:    "Permanent Resident Card",
   279  						Description: "PR card of John Smith.",
   280  						Properties: map[string]*cm.ResolvedProperty{
   281  							"Card Holder's family name": {Label: "Card Holder's family name", Value: "SMITH", Schema: cm.Schema{Type: "string"}},
   282  							"Card Holder's first name":  {Label: "Card Holder's first name", Value: "JOHN", Schema: cm.Schema{Type: "string"}},
   283  						},
   284  					},
   285  					"udc_output": {
   286  						Title:       "Bachelor's Degree",
   287  						Description: "Awarded for completing a four year program at Example University.",
   288  						Properties: map[string]*cm.ResolvedProperty{
   289  							"Degree":               {Label: "Degree", Value: "BachelorDegree", Schema: cm.Schema{Type: "string"}},
   290  							"Degree Holder's name": {Label: "Degree Holder's name", Value: "Jayden Doe", Schema: cm.Schema{Type: "string"}},
   291  						},
   292  					},
   293  				},
   294  			},
   295  			"single descriptor and credentials for multi descriptor manifest": {
   296  				manifest: credentialManifestMultipleVCs,
   297  				response: vpWithDriversLicenseVCAndCredentialResponse,
   298  				expected: map[string]*match{
   299  					"driver_license_output": {
   300  						Title:    "Washington State Driver License",
   301  						Subtitle: "Class A, Commercial",
   302  						Description: "License to operate a vehicle with a gross combined weight " +
   303  							"rating (GCWR) of 26,001 or more pounds, as long as the GVWR of the vehicle(s) " +
   304  							"being towed is over 10,000 pounds.",
   305  						Properties: map[string]*cm.ResolvedProperty{
   306  							"Driving License Number": {Label: "Driving License Number", Value: "34DGE352", Schema: cm.Schema{Type: "boolean"}},
   307  						},
   308  					},
   309  				},
   310  			},
   311  		}
   312  
   313  		t.Parallel()
   314  
   315  		for testName, testData := range testTable {
   316  			t.Run(testName, func(t *testing.T) {
   317  				response := makePresentationFromBytes(t, testData.response, testName)
   318  				manifest := &cm.CredentialManifest{}
   319  				require.NoError(t, manifest.UnmarshalJSON(testData.manifest))
   320  
   321  				results, err := manifest.ResolveResponse(response)
   322  				require.NoError(t, err)
   323  				require.Len(t, results, len(testData.expected))
   324  
   325  				for _, r := range results {
   326  					require.NotEmpty(t, r.DescriptorID)
   327  					expected, ok := testData.expected[r.DescriptorID]
   328  					require.True(t, ok, "unexpected descriptor ID '%s' in resolved properties", r.DescriptorID)
   329  					require.Equal(t, expected.Title, r.Title)
   330  					require.Equal(t, expected.Subtitle, r.Subtitle)
   331  					require.Equal(t, expected.Description, r.Description)
   332  					require.NotEmpty(t, r.Styles.Background)
   333  					require.Len(t, r.Properties, len(expected.Properties))
   334  
   335  					for _, resolvedProperty := range r.Properties {
   336  						expectedVal, ok := expected.Properties[resolvedProperty.Label]
   337  						require.True(t, ok, "expected to find '%s' label in resolved properties", resolvedProperty.Label)
   338  						require.EqualValues(t, expectedVal, resolvedProperty)
   339  					}
   340  				}
   341  			})
   342  		}
   343  	})
   344  
   345  	t.Run("Response format failures", func(t *testing.T) {
   346  		response := makePresentationFromBytes(t, vpMultipleWithCredentialResponse, t.Name())
   347  		manifest := &cm.CredentialManifest{}
   348  		require.NoError(t, manifest.UnmarshalJSON(credentialManifestMultipleVCs))
   349  
   350  		t.Parallel()
   351  
   352  		testTable := map[string]struct {
   353  			credResponse []byte
   354  			error        string
   355  		}{
   356  			"missing credential": {
   357  				credResponse: []byte(`{
   358                        "id":"a30e3b91-fb77-4d22-95fa-871689c322e2",
   359                        "manifest_id":"dcc75a16-19f5-4273-84ce-4da69ee2b7fe",
   360                        "descriptor_map":[
   361                          {
   362                            "id":"udc_output",
   363                            "format":"ldp_vc",
   364                            "path":"$.verifiableCredential[0]"
   365                          },
   366                          {
   367                            "id":"prc_output",
   368                            "format":"ldp_vc",
   369                            "path":"$.verifiableCredential[5]"
   370                          }
   371                        ]
   372                      }`),
   373  				error: "failed to select vc from descriptor",
   374  			},
   375  			"missing path in descriptor": {
   376  				credResponse: []byte(`{
   377                        "id":"a30e3b91-fb77-4d22-95fa-871689c322e2",
   378                        "manifest_id":"dcc75a16-19f5-4273-84ce-4da69ee2b7fe",
   379                        "descriptor_map":[
   380                          {
   381                            "id":"udc_output",
   382                            "format":"ldp_vc"
   383                          },
   384                          {
   385                            "id":"prc_output",
   386                            "format":"ldp_vc",
   387                            "path":"$.verifiableCredential[5]"
   388                          }
   389                        ]
   390                      }`),
   391  				error: "invalid credential path",
   392  			},
   393  			"descriptor id missing": {
   394  				credResponse: []byte(`{
   395                        "id":"a30e3b91-fb77-4d22-95fa-871689c322e2",
   396                        "manifest_id":"dcc75a16-19f5-4273-84ce-4da69ee2b7fe",
   397                        "descriptor_map":[
   398                          {
   399                            "format":"ldp_vc",
   400                            "path":"$.verifiableCredential[0]"
   401                          }
   402                        ]
   403                      }`),
   404  				error: "invalid descriptor ID",
   405  			},
   406  			"incorrect descriptor format": {
   407  				credResponse: []byte(`{
   408                        "id":"a30e3b91-fb77-4d22-95fa-871689c322e2",
   409                        "manifest_id":"dcc75a16-19f5-4273-84ce-4da69ee2b7fe",
   410                        "descriptor_map":[
   411                            "format", "ldp_vc"
   412                        ]
   413                      }`),
   414  				error: "invalid descriptor format",
   415  			},
   416  			"empty descriptor map": {
   417  				credResponse: []byte(`{
   418                        "id":"a30e3b91-fb77-4d22-95fa-871689c322e2",
   419                        "manifest_id":"dcc75a16-19f5-4273-84ce-4da69ee2b7fe",
   420                        "descriptor_map":[]
   421                      }`),
   422  				error: "",
   423  			},
   424  			"invalid descriptor format": {
   425  				credResponse: []byte(`{
   426                        "id":"a30e3b91-fb77-4d22-95fa-871689c322e2",
   427                        "manifest_id":"dcc75a16-19f5-4273-84ce-4da69ee2b7fe",
   428                        "descriptor_map": {}
   429                      }`),
   430  				error: "invalid descriptor map",
   431  			},
   432  			"not matching manifest": {
   433  				credResponse: []byte(`{
   434                        "id":"a30e3b91-fb77-4d22-95fa-871689c322e2",
   435                        "manifest_id":"invalid",
   436                        "descriptor_map": {}
   437                      }`),
   438  				error: "credential response not matching",
   439  			},
   440  			"matching descriptor not found in manifest": {
   441  				credResponse: []byte(`{
   442                        "id":"a30e3b91-fb77-4d22-95fa-871689c322e2",
   443                        "manifest_id":"dcc75a16-19f5-4273-84ce-4da69ee2b7fe",
   444                        "descriptor_map":[
   445                          {
   446                            "id":"udc_output_missing",
   447                            "format":"ldp_vc",
   448                            "path":"$.verifiableCredential[0]"
   449                          },
   450                          {
   451                            "id":"prc_output",
   452                            "format":"ldp_vc",
   453                            "path":"$.verifiableCredential[1]"
   454                          }
   455                        ]
   456                      }`),
   457  				error: "unable to find matching output descriptor from manifest",
   458  			},
   459  		}
   460  
   461  		for testName, testData := range testTable {
   462  			t.Run(testName, func(t *testing.T) {
   463  				var credResponse map[string]interface{}
   464  				require.NoError(t, json.Unmarshal(testData.credResponse, &credResponse))
   465  
   466  				response.CustomFields["credential_response"] = credResponse
   467  
   468  				results, err := manifest.ResolveResponse(response)
   469  				require.Empty(t, results)
   470  				if testData.error == "" {
   471  					require.NoError(t, err)
   472  
   473  					return
   474  				}
   475  
   476  				require.Error(t, err)
   477  				require.Contains(t, err.Error(), testData.error)
   478  			})
   479  		}
   480  	})
   481  
   482  	t.Run("Failures", func(t *testing.T) {
   483  		response := makePresentationFromBytes(t, vpMultipleWithCredentialResponse, t.Name())
   484  		manifest := &cm.CredentialManifest{}
   485  		require.NoError(t, manifest.UnmarshalJSON(credentialManifestMultipleVCs))
   486  
   487  		// resolve err (resolved value is not string)
   488  		for _, descr := range manifest.OutputDescriptors {
   489  			if descr.ID == "udc_output" {
   490  				incorrectProperties := `[{
   491                      "path":[
   492                        "$."
   493                      ],
   494                      "schema": {
   495                        "type": "string"
   496                      },
   497                      "fallback":"Unknown",
   498                      "label":"Driving License Number"
   499                    }]`
   500  
   501  				require.NoError(t, json.Unmarshal([]byte(incorrectProperties), &descr.Display.Properties))
   502  			}
   503  		}
   504  		results, err := manifest.ResolveResponse(response)
   505  		require.Empty(t, results)
   506  		require.Error(t, err)
   507  		require.Contains(t, err.Error(), "failed to resolve credential by descriptor")
   508  
   509  		// unsupported formats
   510  		var credResponse map[string]interface{}
   511  		require.NoError(t, json.Unmarshal([]byte(`{
   512                        "id":"a30e3b91-fb77-4d22-95fa-871689c322e2",
   513                        "manifest_id":"dcc75a16-19f5-4273-84ce-4da69ee2b7fe",
   514                        "descriptor_map":[
   515                          {
   516                            "id":"udc_output",
   517                            "format":"jwt_vc",
   518                            "path":"$.verifiableCredential[0]"
   519                          },
   520                          {
   521                            "id":"prc_output",
   522                            "format":"jwt_vc",
   523                            "path":"$.verifiableCredential[1]"
   524                          }
   525                        ]
   526                      }`), &credResponse))
   527  
   528  		response.CustomFields["credential_response"] = credResponse
   529  
   530  		results, err = manifest.ResolveResponse(response)
   531  		require.Empty(t, results)
   532  		require.NoError(t, err)
   533  
   534  		// marshal presentation error
   535  		response.CustomFields["invalid"] = make(chan int)
   536  		results, err = manifest.ResolveResponse(response)
   537  		require.Empty(t, results)
   538  		require.Error(t, err)
   539  		require.Contains(t, err.Error(), "failed to marshal vp")
   540  
   541  		// missing credential response
   542  		delete(response.CustomFields, "credential_response")
   543  		results, err = manifest.ResolveResponse(response)
   544  		require.Empty(t, results)
   545  		require.Error(t, err)
   546  		require.Contains(t, err.Error(), "invalid credential response")
   547  	})
   548  }
   549  
   550  func TestResolveCredential(t *testing.T) {
   551  	t.Run("Successes", func(t *testing.T) {
   552  		testCases := []struct {
   553  			name         string
   554  			credResolver func(t *testing.T) cm.CredentialToResolveOption
   555  		}{
   556  			{
   557  				name: "resolve credential struct",
   558  				credResolver: func(t *testing.T) cm.CredentialToResolveOption {
   559  					t.Helper()
   560  
   561  					vc := parseTestCredential(t, validVC)
   562  
   563  					return cm.CredentialToResolve(vc)
   564  				},
   565  			},
   566  			{
   567  				name: "resolve raw JSON-LD credential",
   568  				credResolver: func(t *testing.T) cm.CredentialToResolveOption {
   569  					t.Helper()
   570  
   571  					return cm.RawCredentialToResolve(validVC)
   572  				},
   573  			},
   574  			{
   575  				name: "resolve raw JWT credential",
   576  				credResolver: func(t *testing.T) cm.CredentialToResolveOption {
   577  					t.Helper()
   578  
   579  					return cm.RawCredentialToResolve(validJWTVC)
   580  				},
   581  			},
   582  		}
   583  
   584  		for _, tc := range testCases {
   585  			t.Run(tc.name, func(t *testing.T) {
   586  				manifest := &cm.CredentialManifest{}
   587  				require.NoError(t, manifest.UnmarshalJSON(credentialManifestUniversityDegree))
   588  
   589  				result, err := manifest.ResolveCredential("bachelors_degree", tc.credResolver(t))
   590  				require.NoError(t, err)
   591  				require.NotEmpty(t, result)
   592  				require.Equal(t, result.Title, "Bachelor of Applied Science")
   593  				require.Equal(t, result.Subtitle, "Electrical Systems Specialty")
   594  
   595  				expected := map[string]*cm.ResolvedProperty{
   596  					"With distinction": {
   597  						Label: "With distinction",
   598  						Value: true,
   599  						Schema: cm.Schema{
   600  							Type: "boolean",
   601  						},
   602  					},
   603  					"Years studied": {
   604  						Label: "Years studied",
   605  						Value: float64(4),
   606  						Schema: cm.Schema{
   607  							Type: "number",
   608  						},
   609  					},
   610  				}
   611  
   612  				for _, property := range result.Properties {
   613  					expectedVal, ok := expected[property.Label]
   614  					require.True(t, ok, "unexpected label '%s' in resolved properties", property.Label)
   615  					require.EqualValues(t, expectedVal, property)
   616  				}
   617  			})
   618  		}
   619  	})
   620  
   621  	t.Run("Failures", func(t *testing.T) {
   622  		manifest := &cm.CredentialManifest{}
   623  		require.NoError(t, manifest.UnmarshalJSON(credentialManifestUniversityDegree))
   624  
   625  		// invalid credential to resolve
   626  		result, err := manifest.ResolveCredential("bachelors_degree", nil)
   627  		require.Empty(t, result)
   628  		require.Error(t, err)
   629  		require.Contains(t, err.Error(), "credential to resolve is not provided")
   630  
   631  		// invalid raw credential to resolve
   632  		result, err = manifest.ResolveCredential("bachelors_degree", cm.RawCredentialToResolve([]byte("---")))
   633  		require.Empty(t, result)
   634  		require.Error(t, err)
   635  		require.Contains(t, err.Error(), "invalid character")
   636  
   637  		vc := parseTestCredential(t, validVC)
   638  
   639  		// descriptor not found
   640  		result, err = manifest.ResolveCredential("bachelors_degree_incorrect", cm.CredentialToResolve(vc))
   641  		require.Empty(t, result)
   642  		require.Error(t, err)
   643  		require.Contains(t, err.Error(), "unable to find matching descriptor")
   644  
   645  		// credential marshal error
   646  		vc.CustomFields["invalid"] = make(chan int)
   647  
   648  		result, err = manifest.ResolveCredential("bachelors_degree", cm.CredentialToResolve(vc))
   649  		require.Empty(t, result)
   650  		require.Error(t, err)
   651  		require.Contains(t, err.Error(), "JSON marshalling of verifiable credential")
   652  	})
   653  }
   654  
   655  func createMarshalledCredentialManifestWithInvalidTitleJSONPath(t *testing.T) []byte {
   656  	credentialManifest := createCredentialManifestWithInvalidTitleJSONPath(t)
   657  
   658  	credentialManifestWithInvalidJSONPathBytes, err := json.Marshal(credentialManifest)
   659  	require.NoError(t, err)
   660  
   661  	return credentialManifestWithInvalidJSONPathBytes
   662  }
   663  
   664  func createCredentialManifestWithInvalidTitleJSONPath(t *testing.T) cm.CredentialManifest {
   665  	credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   666  
   667  	credentialManifest.OutputDescriptors[0].Display.Title.Paths[0] = invalidJSONPath
   668  
   669  	return credentialManifest
   670  }
   671  
   672  func createMarshalledCredentialManifestWithInvalidSubtitleJSONPath(t *testing.T) []byte {
   673  	credentialManifest := createCredentialManifestWithInvalidSubtitleJSONPath(t)
   674  
   675  	credentialManifestWithInvalidJSONPathBytes, err := json.Marshal(credentialManifest)
   676  	require.NoError(t, err)
   677  
   678  	return credentialManifestWithInvalidJSONPathBytes
   679  }
   680  
   681  func createCredentialManifestWithInvalidSubtitleJSONPath(t *testing.T) cm.CredentialManifest {
   682  	credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   683  
   684  	credentialManifest.OutputDescriptors[0].Display.Subtitle.Paths[0] = invalidJSONPath
   685  
   686  	return credentialManifest
   687  }
   688  
   689  func createMarshalledCredentialManifestWithInvalidDescriptionJSONPath(t *testing.T) []byte {
   690  	credentialManifest := createCredentialManifestWithInvalidDescriptionJSONPath(t)
   691  
   692  	credentialManifestWithInvalidJSONPathBytes, err := json.Marshal(credentialManifest)
   693  	require.NoError(t, err)
   694  
   695  	return credentialManifestWithInvalidJSONPathBytes
   696  }
   697  
   698  func createCredentialManifestWithInvalidDescriptionJSONPath(t *testing.T) cm.CredentialManifest {
   699  	credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   700  
   701  	credentialManifest.OutputDescriptors[0].Display.Description.Paths = []string{invalidJSONPath}
   702  
   703  	return credentialManifest
   704  }
   705  
   706  func createMarshalledCredentialManifestWithInvalidPropertyJSONPath(t *testing.T) []byte {
   707  	credentialManifest := createCredentialManifestWithInvalidPropertyJSONPath(t)
   708  
   709  	credentialManifestWithInvalidJSONPathBytes, err := json.Marshal(credentialManifest)
   710  	require.NoError(t, err)
   711  
   712  	return credentialManifestWithInvalidJSONPathBytes
   713  }
   714  
   715  func createCredentialManifestWithInvalidPropertyJSONPath(t *testing.T) cm.CredentialManifest {
   716  	credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   717  
   718  	credentialManifest.OutputDescriptors[0].Display.Properties[0].Paths[0] = invalidJSONPath
   719  
   720  	return credentialManifest
   721  }
   722  
   723  func createMarshalledCredentialManifestWithInvalidSchemaType(t *testing.T) []byte {
   724  	credentialManifest := createCredentialManifestWithInvalidSchemaType(t)
   725  
   726  	credentialManifestWithInvalidJSONPathBytes, err := json.Marshal(credentialManifest)
   727  	require.NoError(t, err)
   728  
   729  	return credentialManifestWithInvalidJSONPathBytes
   730  }
   731  
   732  func createCredentialManifestWithInvalidSchemaType(t *testing.T) cm.CredentialManifest {
   733  	credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   734  
   735  	credentialManifest.OutputDescriptors[0].Display.Title.Schema.Type = "InvalidSchemaType"
   736  
   737  	return credentialManifest
   738  }
   739  
   740  func createMarshalledCredentialManifestWithInvalidSchemaFormat(t *testing.T) []byte {
   741  	credentialManifest := createCredentialManifestWithInvalidSchemaFormat(t)
   742  
   743  	credentialManifestWithInvalidJSONPathBytes, err := json.Marshal(credentialManifest)
   744  	require.NoError(t, err)
   745  
   746  	return credentialManifestWithInvalidJSONPathBytes
   747  }
   748  
   749  func createCredentialManifestWithInvalidSchemaFormat(t *testing.T) cm.CredentialManifest {
   750  	credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   751  
   752  	credentialManifest.OutputDescriptors[0].Display.Title.Schema = cm.Schema{
   753  		Type:   "string",
   754  		Format: "UnknownFormat",
   755  	}
   756  
   757  	return credentialManifest
   758  }
   759  
   760  func createCredentialManifestWithMissingPropertyJSONPathAndText(t *testing.T) cm.CredentialManifest {
   761  	credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   762  
   763  	credentialManifest.OutputDescriptors[0].Display.Properties[0].Paths = []string{}
   764  	credentialManifest.OutputDescriptors[0].Display.Properties[0].Text = ""
   765  
   766  	return credentialManifest
   767  }
   768  
   769  func createMarshalledCredentialManifestWithMissingPropertyJSONPathAndText(t *testing.T) []byte {
   770  	credentialManifest := createCredentialManifestWithMissingPropertyJSONPathAndText(t)
   771  
   772  	credentialManifestWithNoPathsOrType, err := json.Marshal(credentialManifest)
   773  	require.NoError(t, err)
   774  
   775  	return credentialManifestWithNoPathsOrType
   776  }
   777  
   778  func createCredentialManifestWithNilJWTFormat(t *testing.T) cm.CredentialManifest {
   779  	credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegreeWithFormat)
   780  
   781  	credentialManifest.Format.Jwt = nil
   782  
   783  	return credentialManifest
   784  }
   785  
   786  func createCredentialManifestWithNilLDPFormat(t *testing.T) cm.CredentialManifest {
   787  	credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegreeWithFormat)
   788  
   789  	credentialManifest.Format.Ldp = nil
   790  
   791  	return credentialManifest
   792  }
   793  
   794  func createCredentialManifestWithNoImageURI(t *testing.T) cm.CredentialManifest {
   795  	credentialManifest := makeCredentialManifestFromBytes(t, credentialManifestUniversityDegree)
   796  
   797  	credentialManifest.OutputDescriptors[0].Styles.Thumbnail = &cm.ImageURIWithAltText{
   798  		Alt: "valid alt",
   799  	}
   800  	require.Empty(t, credentialManifest.OutputDescriptors[0].Styles.Thumbnail.URI)
   801  
   802  	return credentialManifest
   803  }
   804  
   805  func makeCredentialManifestFromBytes(t *testing.T, credentialManifestBytes []byte) cm.CredentialManifest {
   806  	var credentialManifest cm.CredentialManifest
   807  
   808  	err := json.Unmarshal(credentialManifestBytes, &credentialManifest)
   809  	require.NoError(t, err)
   810  
   811  	return credentialManifest
   812  }
   813  
   814  func parseTestCredential(t *testing.T, vcData []byte) *verifiable.Credential {
   815  	t.Helper()
   816  
   817  	vc, err := verifiable.ParseCredential(vcData, verifiable.WithJSONLDDocumentLoader(createTestDocumentLoader(t)))
   818  	require.NoError(t, err)
   819  
   820  	return vc
   821  }
   822  
   823  func createTestDocumentLoader(t *testing.T) *ld.DocumentLoader {
   824  	t.Helper()
   825  
   826  	loader, err := ldtestutil.DocumentLoader()
   827  	require.NoError(t, err)
   828  
   829  	return loader
   830  }