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

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  SPDX-License-Identifier: Apache-2.0
     4  */
     5  
     6  package verifiable
     7  
     8  import (
     9  	_ "embed"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"testing"
    14  
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/hyperledger/aries-framework-go/pkg/doc/ldcontext"
    18  )
    19  
    20  const (
    21  	validCred1 = `
    22  {
    23    "@context": [
    24      "https://www.w3.org/2018/credentials/v1",
    25      "%s"
    26    ],
    27    "id": "http://example.edu/credentials/1872",
    28    "type": [
    29      "VerifiableCredential",
    30      "CredType1"
    31    ],
    32    "credentialSubject": {
    33      "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
    34      "s1": "custom subject 1"
    35    },
    36  
    37    "c1": "custom field 1",
    38  
    39    "issuer": {
    40      "id": "did:example:76e12ec712ebc6f1c221ebfeb1f",
    41      "name": "Example University"
    42    },
    43  
    44    "issuanceDate": "2010-01-01T19:23:24Z"
    45  }
    46  `
    47  
    48  	validCred2 = `
    49  {
    50    "@context": [
    51      "https://www.w3.org/2018/credentials/v1",
    52      "%s"
    53    ],
    54    "id": "http://example.edu/credentials/1872",
    55    "type": [
    56      "VerifiableCredential",
    57      "CredType2"
    58    ],
    59    "credentialSubject": {
    60      "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
    61      "s2": "custom subject 2"
    62    },
    63  
    64    "c2": "custom field 2",
    65  
    66    "issuer": {
    67      "id": "did:example:76e12ec712ebc6f1c221ebfeb1f",
    68      "name": "Example University"
    69    },
    70  
    71    "issuanceDate": "2010-01-01T19:23:24Z"
    72  }`
    73  
    74  	credMissingMandatoryFields = `
    75  {
    76    "@context": [
    77      "https://www.w3.org/2018/credentials/v1",
    78      "https://www.w3.org/2018/credentials/examples/ext/type2"
    79    ],
    80    "id": "http://example.edu/credentials/1872",
    81    "type": [
    82      "VerifiableCredential"
    83    ]
    84  }`
    85  )
    86  
    87  //nolint:gochecknoglobals
    88  var (
    89  	//go:embed testdata/context/context1.jsonld
    90  	context1 []byte
    91  	//go:embed testdata/context/context2.jsonld
    92  	context2 []byte
    93  )
    94  
    95  // Cred1 can produce itself.
    96  type Cred1 struct {
    97  	Base    Credential
    98  	Subject struct {
    99  		ID                 string `json:"id,omitempty"`
   100  		CustomSubjectField string `json:"s1,omitempty"`
   101  	} `json:"credentialSubject,omitempty"`
   102  	CustomField string `json:"c1,omitempty"`
   103  }
   104  
   105  func (c1 *Cred1) Accept(vc *Credential) bool {
   106  	return hasType(vc.Types, "CredType1")
   107  }
   108  
   109  func (c1 *Cred1) Apply(vc *Credential, dataJSON []byte) (interface{}, error) {
   110  	err := json.Unmarshal(dataJSON, c1)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	c1.Base = *vc
   116  
   117  	return c1, nil
   118  }
   119  
   120  // There is a separate producer for Cred2.
   121  type Cred2 struct {
   122  	Base    Credential
   123  	Subject struct {
   124  		ID                 string `json:"id,omitempty"`
   125  		CustomSubjectField string `json:"s2,omitempty"`
   126  	} `json:"credentialSubject,omitempty"`
   127  	CustomField string `json:"c2,omitempty"`
   128  }
   129  
   130  type Cred2Producer struct{}
   131  
   132  func (c2p *Cred2Producer) Accept(vc *Credential) bool {
   133  	return hasType(vc.Types, "CredType2")
   134  }
   135  
   136  func (c2p *Cred2Producer) Apply(vc *Credential, dataJSON []byte) (interface{}, error) {
   137  	c2 := Cred2{}
   138  
   139  	err := json.Unmarshal(dataJSON, &c2)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	c2.Base = *vc
   145  
   146  	return &c2, nil
   147  }
   148  
   149  func NewCred1Producer() CustomCredentialProducer {
   150  	return &Cred1{}
   151  }
   152  
   153  func NewCred2Producer() CustomCredentialProducer {
   154  	return &Cred2Producer{}
   155  }
   156  
   157  type FailingCredentialProducer struct{}
   158  
   159  func (fp *FailingCredentialProducer) Accept(*Credential) bool {
   160  	return true
   161  }
   162  
   163  func (fp *FailingCredentialProducer) Apply(*Credential, []byte) (interface{}, error) {
   164  	return nil, errors.New("failed to apply credential extension")
   165  }
   166  
   167  func hasType(allTypes []string, targetType string) bool {
   168  	for _, thatType := range allTypes {
   169  		if thatType == targetType {
   170  			return true
   171  		}
   172  	}
   173  
   174  	return false
   175  }
   176  
   177  func TestCredentialExtensibilitySwitch(t *testing.T) {
   178  	producers := []CustomCredentialProducer{NewCred1Producer(), NewCred2Producer()}
   179  
   180  	contextURL := "http://127.0.0.1"
   181  
   182  	// Producer1 applied.
   183  	i1, err := createTestCustomCredential(t, []byte(fmt.Sprintf(validCred1, contextURL+"?context=1")), producers,
   184  		ldcontext.Document{
   185  			URL:     "http://127.0.0.1?context=1",
   186  			Content: context1,
   187  		})
   188  	require.NoError(t, err)
   189  	require.IsType(t, &Cred1{}, i1)
   190  	cred1, correct := i1.(*Cred1)
   191  	require.True(t, correct)
   192  	require.NotNil(t, cred1.Base)
   193  	require.Equal(t, []string{"VerifiableCredential", "CredType1"}, cred1.Base.Types)
   194  	require.Equal(t, "custom field 1", cred1.CustomField)
   195  	require.Equal(t, "custom subject 1", cred1.Subject.CustomSubjectField)
   196  
   197  	// Producer2 applied.
   198  	i2, err := createTestCustomCredential(t, []byte(fmt.Sprintf(validCred2, contextURL+"?context=2")), producers,
   199  		ldcontext.Document{
   200  			URL:     "http://127.0.0.1?context=2",
   201  			Content: context2,
   202  		})
   203  	require.NoError(t, err)
   204  	require.IsType(t, &Cred2{}, i2)
   205  	cred2, correct := i2.(*Cred2)
   206  	require.True(t, correct)
   207  	require.NotNil(t, cred2.Base)
   208  	require.Equal(t, []string{"VerifiableCredential", "CredType2"}, cred2.Base.Types)
   209  	require.Equal(t, "custom field 2", cred2.CustomField)
   210  	require.Equal(t, "custom subject 2", cred2.Subject.CustomSubjectField)
   211  
   212  	// No producers are applied, returned base credential.
   213  	i3, err := createTestCustomCredential(t, []byte(validCredential), producers)
   214  	require.NoError(t, err)
   215  	require.IsType(t, &Credential{}, i3)
   216  
   217  	// Invalid credential.
   218  	i4, err := createTestCustomCredential(t, []byte(credMissingMandatoryFields), producers)
   219  	require.Error(t, err)
   220  	require.Contains(t, err.Error(), "build base verifiable credential")
   221  	require.Nil(t, i4)
   222  
   223  	// Failing ext producer.
   224  	i5, err := createTestCustomCredential(t, []byte(validCredential),
   225  		[]CustomCredentialProducer{&FailingCredentialProducer{}})
   226  	require.Error(t, err)
   227  	require.Contains(t, err.Error(), "failed to apply credential extension")
   228  	require.Nil(t, i5)
   229  }
   230  
   231  func createTestCustomCredential(t *testing.T, vcData []byte, producers []CustomCredentialProducer,
   232  	extraContexts ...ldcontext.Document) (interface{}, error) {
   233  	t.Helper()
   234  
   235  	return CreateCustomCredential(vcData, producers,
   236  		WithJSONLDDocumentLoader(createTestDocumentLoader(t, extraContexts...)))
   237  }