github.com/hyperledger/aries-framework-go@v0.3.2/pkg/client/outofband/client_test.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package outofband
     8  
     9  import (
    10  	"encoding/base64"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/google/uuid"
    18  	"github.com/stretchr/testify/require"
    19  
    20  	commonmodel "github.com/hyperledger/aries-framework-go/pkg/common/model"
    21  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
    22  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator"
    23  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/didexchange"
    24  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/mediator"
    25  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/outofband"
    26  	"github.com/hyperledger/aries-framework-go/pkg/doc/did"
    27  	"github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr"
    28  	mockroute "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/mediator"
    29  	mockkms "github.com/hyperledger/aries-framework-go/pkg/mock/kms"
    30  	mockprovider "github.com/hyperledger/aries-framework-go/pkg/mock/provider"
    31  	mockstore "github.com/hyperledger/aries-framework-go/pkg/mock/storage"
    32  )
    33  
    34  // Ensure Client can emit events.
    35  var _ service.Event = (*Client)(nil)
    36  
    37  func TestNew(t *testing.T) {
    38  	t.Run("returns client", func(t *testing.T) {
    39  		c, err := New(withTestProvider())
    40  		require.NoError(t, err)
    41  		require.NotNil(t, c)
    42  		require.NotNil(t, c.Event)
    43  	})
    44  }
    45  
    46  func TestCreateInvitation(t *testing.T) {
    47  	t.Run("sets an id", func(t *testing.T) {
    48  		c, err := New(withTestProvider())
    49  		require.NoError(t, err)
    50  		inv, err := c.CreateInvitation(nil)
    51  		require.NoError(t, err)
    52  		require.NotEmpty(t, inv.ID)
    53  	})
    54  	t.Run("sets correct type", func(t *testing.T) {
    55  		c, err := New(withTestProvider())
    56  		require.NoError(t, err)
    57  		inv, err := c.CreateInvitation(nil)
    58  		require.NoError(t, err)
    59  		require.Equal(t, "https://didcomm.org/out-of-band/1.0/invitation", inv.Type)
    60  	})
    61  	t.Run("sets explicit protocols", func(t *testing.T) {
    62  		expected := []string{"protocol1", "protocol2"}
    63  		c, err := New(withTestProvider())
    64  		require.NoError(t, err)
    65  		inv, err := c.CreateInvitation(nil, WithHandshakeProtocols(expected...))
    66  		require.NoError(t, err)
    67  		require.Equal(t, expected, inv.Protocols)
    68  	})
    69  	t.Run("sets default protocols", func(t *testing.T) {
    70  		c, err := New(withTestProvider())
    71  		require.NoError(t, err)
    72  		inv, err := c.CreateInvitation(nil)
    73  		require.NoError(t, err)
    74  		require.Equal(t, []string{didexchange.PIURI}, inv.Protocols)
    75  	})
    76  	t.Run("includes the diddoc Service block returned by provider", func(t *testing.T) {
    77  		expected := &did.Service{
    78  			ID:              uuid.New().String(),
    79  			Type:            uuid.New().String(),
    80  			Priority:        0,
    81  			RecipientKeys:   []string{uuid.New().String()},
    82  			ServiceEndpoint: commonmodel.NewDIDCommV1Endpoint(uuid.New().String()),
    83  			RoutingKeys:     []string{uuid.New().String()},
    84  			Properties:      nil,
    85  		}
    86  		c, err := New(withTestProvider())
    87  		require.NoError(t, err)
    88  		c.didDocSvcFunc = func(_ string, _ []string) (*did.Service, error) {
    89  			return expected, nil
    90  		}
    91  		inv, err := c.CreateInvitation(nil)
    92  		require.NoError(t, err)
    93  		require.Len(t, inv.Services, 1)
    94  		require.Equal(t, expected, inv.Services[0])
    95  	})
    96  	t.Run("includes didDocSvcFunc returning error", func(t *testing.T) {
    97  		c, err := New(withTestProvider())
    98  		require.NoError(t, err)
    99  		c.didDocSvcFunc = func(_ string, _ []string) (*did.Service, error) {
   100  			return nil, fmt.Errorf("error didDocServiceFunction")
   101  		}
   102  		_, err = c.CreateInvitation(nil)
   103  		require.EqualError(t, err, "failed to create a new inlined did doc service block : error didDocServiceFunction")
   104  	})
   105  	t.Run("create invitation with invalid service", func(t *testing.T) {
   106  		c, err := New(withTestProvider())
   107  		require.NoError(t, err)
   108  
   109  		_, err = c.CreateInvitation([]interface{}{"invalid"})
   110  		require.EqualError(t, err, "invalid service: invalid DID [invalid]: invalid did: invalid. Make sure it "+
   111  			"conforms to the DID syntax: https://w3c.github.io/did-core/#did-syntax")
   112  	})
   113  	t.Run("WithLabel", func(t *testing.T) {
   114  		c, err := New(withTestProvider())
   115  		require.NoError(t, err)
   116  		expected := uuid.New().String()
   117  		inv, err := c.CreateInvitation(nil, WithLabel(expected))
   118  		require.NoError(t, err)
   119  		require.Equal(t, expected, inv.Label)
   120  	})
   121  	t.Run("with router connection", func(t *testing.T) {
   122  		const expectedConn = "conn-xyz"
   123  
   124  		c, err := New(withTestProvider())
   125  		require.NoError(t, err)
   126  
   127  		c.didDocSvcFunc = func(conn string, accept []string) (*did.Service, error) {
   128  			require.Equal(t, expectedConn, conn)
   129  
   130  			var svc *did.Service
   131  
   132  			if isDIDCommV2(accept) {
   133  				svc = &did.Service{
   134  					ServiceEndpoint: commonmodel.NewDIDCommV2Endpoint([]commonmodel.DIDCommV2Endpoint{
   135  						{URI: expectedConn, Accept: accept},
   136  					}),
   137  					Type: vdr.DIDCommV2ServiceType,
   138  				}
   139  			} else {
   140  				svc = &did.Service{
   141  					ServiceEndpoint: commonmodel.NewDIDCommV1Endpoint(expectedConn),
   142  					Accept:          accept,
   143  					Type:            vdr.DIDCommServiceType,
   144  				}
   145  			}
   146  
   147  			return svc, nil
   148  		}
   149  
   150  		inv, err := c.CreateInvitation(nil, WithRouterConnections(expectedConn))
   151  		require.NoError(t, err)
   152  		uri, err := inv.Services[0].(*did.Service).ServiceEndpoint.URI()
   153  		require.NoError(t, err)
   154  		require.Equal(t, expectedConn, uri)
   155  	})
   156  	t.Run("WithGoal", func(t *testing.T) {
   157  		c, err := New(withTestProvider())
   158  		require.NoError(t, err)
   159  		expectedGoal := uuid.New().String()
   160  		expectedGoalCode := uuid.New().String()
   161  		inv, err := c.CreateInvitation(nil, WithGoal(expectedGoal, expectedGoalCode))
   162  		require.NoError(t, err)
   163  		require.Equal(t, expectedGoal, inv.Goal)
   164  		require.Equal(t, expectedGoalCode, inv.GoalCode)
   165  	})
   166  	t.Run("WithServices diddoc service blocks", func(t *testing.T) {
   167  		c, err := New(withTestProvider())
   168  		require.NoError(t, err)
   169  		expected := &did.Service{
   170  			ID:              uuid.New().String(),
   171  			Type:            uuid.New().String(),
   172  			Priority:        0,
   173  			RecipientKeys:   []string{uuid.New().String()},
   174  			ServiceEndpoint: commonmodel.NewDIDCommV1Endpoint(uuid.New().String()),
   175  			RoutingKeys:     []string{uuid.New().String()},
   176  			Properties:      nil,
   177  		}
   178  		inv, err := c.CreateInvitation([]interface{}{expected})
   179  		require.NoError(t, err)
   180  		require.Len(t, inv.Services, 1)
   181  		require.Equal(t, expected, inv.Services[0])
   182  	})
   183  	t.Run("WithServices dids", func(t *testing.T) {
   184  		c, err := New(withTestProvider())
   185  		require.NoError(t, err)
   186  		expected := "did:example:234"
   187  		inv, err := c.CreateInvitation([]interface{}{expected})
   188  		require.NoError(t, err)
   189  		require.Len(t, inv.Services, 1)
   190  		require.Equal(t, expected, inv.Services[0])
   191  	})
   192  	t.Run("WithServices dids and diddoc service blocks", func(t *testing.T) {
   193  		c, err := New(withTestProvider())
   194  		require.NoError(t, err)
   195  		didRef := "did:example:234"
   196  		svc := &did.Service{
   197  			ID:              uuid.New().String(),
   198  			Type:            uuid.New().String(),
   199  			Priority:        0,
   200  			RecipientKeys:   []string{uuid.New().String()},
   201  			ServiceEndpoint: commonmodel.NewDIDCommV1Endpoint(uuid.New().String()),
   202  			RoutingKeys:     []string{uuid.New().String()},
   203  			Properties:      nil,
   204  		}
   205  		inv, err := c.CreateInvitation([]interface{}{svc, didRef})
   206  		require.NoError(t, err)
   207  		require.Len(t, inv.Services, 2)
   208  		require.Contains(t, inv.Services, didRef)
   209  		require.Contains(t, inv.Services, svc)
   210  	})
   211  	t.Run("WithAttachments", func(t *testing.T) {
   212  		c, err := New(withTestProvider())
   213  		require.NoError(t, err)
   214  		expected := dummyAttachment(t)
   215  		inv, err := c.CreateInvitation(
   216  			nil,
   217  			WithAttachments(expected),
   218  		)
   219  		require.NoError(t, err)
   220  		require.Contains(t, inv.Requests, expected)
   221  	})
   222  	t.Run("WithAttachments", func(t *testing.T) {
   223  		c, err := New(withTestProvider())
   224  		require.NoError(t, err)
   225  		expected := dummyAttachment(t)
   226  		inv, err := c.CreateInvitation(
   227  			nil,
   228  			WithAttachments(expected),
   229  		)
   230  		require.NoError(t, err)
   231  		require.Contains(t, inv.Requests, expected)
   232  	})
   233  }
   234  
   235  func TestClient_ActionContinue(t *testing.T) {
   236  	const (
   237  		PIID  = "piid"
   238  		label = "label"
   239  	)
   240  
   241  	provider := withTestProvider()
   242  	provider.ServiceMap = map[string]interface{}{
   243  		outofband.Name: &stubOOBService{
   244  			actionContinueFunc: func(piid string, options outofband.Options) error {
   245  				require.Equal(t, PIID, piid)
   246  				require.Equal(t, &EventOptions{Label: label}, options)
   247  
   248  				return nil
   249  			},
   250  		},
   251  	}
   252  	c, err := New(provider)
   253  	require.NoError(t, err)
   254  	require.NoError(t, c.ActionContinue(PIID, label))
   255  }
   256  
   257  func TestClient_ActionStop(t *testing.T) {
   258  	const PIID = "piid"
   259  
   260  	provider := withTestProvider()
   261  	provider.ServiceMap = map[string]interface{}{
   262  		outofband.Name: &stubOOBService{
   263  			actionStopFunc: func(piid string, err error) error {
   264  				require.Equal(t, PIID, piid)
   265  				require.Nil(t, err)
   266  
   267  				return nil
   268  			},
   269  		},
   270  	}
   271  
   272  	c, err := New(provider)
   273  	require.NoError(t, err)
   274  	require.NoError(t, c.ActionStop(PIID, nil))
   275  }
   276  
   277  func TestClient_Actions(t *testing.T) {
   278  	t.Run("Error", func(t *testing.T) {
   279  		provider := withTestProvider()
   280  		provider.ServiceMap = map[string]interface{}{
   281  			outofband.Name: &stubOOBService{
   282  				actionsFunc: func() ([]outofband.Action, error) {
   283  					return nil, errors.New("test")
   284  				},
   285  			},
   286  		}
   287  
   288  		c, err := New(provider)
   289  		require.NoError(t, err)
   290  		actions, err := c.Actions()
   291  		require.EqualError(t, err, "test")
   292  		require.Nil(t, actions)
   293  	})
   294  	t.Run("Success", func(t *testing.T) {
   295  		expected := []outofband.Action{{}, {}}
   296  		provider := withTestProvider()
   297  		provider.ServiceMap = map[string]interface{}{
   298  			outofband.Name: &stubOOBService{
   299  				actionsFunc: func() ([]outofband.Action, error) {
   300  					return expected, nil
   301  				},
   302  			},
   303  		}
   304  
   305  		c, err := New(provider)
   306  		require.NoError(t, err)
   307  		actions, err := c.Actions()
   308  		require.NoError(t, err)
   309  		require.Equal(t, len(expected), len(actions))
   310  	})
   311  }
   312  
   313  func TestAcceptInvitation(t *testing.T) {
   314  	t.Run("returns connection ID", func(t *testing.T) {
   315  		expected := "123456"
   316  		provider := withTestProvider()
   317  		provider.ServiceMap = map[string]interface{}{
   318  			outofband.Name: &stubOOBService{
   319  				acceptInvFunc: func(*outofband.Invitation, outofband.Options) (string, error) {
   320  					return expected, nil
   321  				},
   322  			},
   323  		}
   324  		c, err := New(provider)
   325  		require.NoError(t, err)
   326  		result, err := c.AcceptInvitation(&Invitation{}, "")
   327  		require.NoError(t, err)
   328  		require.Equal(t, expected, result)
   329  	})
   330  	t.Run("wraps error from outofband service", func(t *testing.T) {
   331  		expected := errors.New("test")
   332  		provider := withTestProvider()
   333  		provider.ServiceMap = map[string]interface{}{
   334  			outofband.Name: &stubOOBService{
   335  				acceptInvFunc: func(*outofband.Invitation, outofband.Options) (string, error) {
   336  					return "", expected
   337  				},
   338  			},
   339  		}
   340  		c, err := New(provider)
   341  		require.NoError(t, err)
   342  		_, err = c.AcceptInvitation(&Invitation{}, "")
   343  		require.Error(t, err)
   344  		require.True(t, errors.Is(err, expected))
   345  	})
   346  }
   347  
   348  func dummyAttachment(t *testing.T) *decorator.Attachment {
   349  	t.Helper()
   350  
   351  	return base64Attachment(t, &didcommMsg{
   352  		ID:   uuid.New().String(),
   353  		Type: uuid.New().String(),
   354  	})
   355  }
   356  
   357  func base64Attachment(t *testing.T, data interface{}) *decorator.Attachment {
   358  	t.Helper()
   359  
   360  	bytes, err := json.Marshal(data)
   361  	require.NoError(t, err)
   362  
   363  	return &decorator.Attachment{
   364  		ID:          uuid.New().String(),
   365  		Description: uuid.New().String(),
   366  		FileName:    uuid.New().String(),
   367  		MimeType:    uuid.New().String(),
   368  		LastModTime: time.Now(),
   369  		ByteCount:   0,
   370  		Data: decorator.AttachmentData{
   371  			Base64: base64.StdEncoding.EncodeToString(bytes),
   372  		},
   373  	}
   374  }
   375  
   376  type didcommMsg struct {
   377  	ID   string
   378  	Type string
   379  }
   380  
   381  func withTestProvider() *mockprovider.Provider {
   382  	mockKey, err := mockkms.CreateMockED25519KeyHandle()
   383  	if err != nil {
   384  		return nil
   385  	}
   386  
   387  	return &mockprovider.Provider{
   388  		ProtocolStateStorageProviderValue: mockstore.NewMockStoreProvider(),
   389  		StorageProviderValue:              mockstore.NewMockStoreProvider(),
   390  		KMSValue:                          &mockkms.KeyManager{CreateKeyValue: mockKey},
   391  		ServiceMap: map[string]interface{}{
   392  			mediator.Coordination: &mockroute.MockMediatorSvc{Connections: []string{"xyz"}},
   393  			outofband.Name:        &stubOOBService{},
   394  		},
   395  		ServiceEndpointValue: "endpoint",
   396  	}
   397  }
   398  
   399  type stubOOBService struct {
   400  	service.Event
   401  	acceptInvFunc      func(*outofband.Invitation, outofband.Options) (string, error)
   402  	saveInvFunc        func(*outofband.Invitation) error
   403  	actionsFunc        func() ([]outofband.Action, error)
   404  	actionContinueFunc func(string, outofband.Options) error
   405  	actionStopFunc     func(piid string, err error) error
   406  }
   407  
   408  func (s *stubOOBService) AcceptInvitation(i *outofband.Invitation, o outofband.Options) (string, error) {
   409  	if s.acceptInvFunc != nil {
   410  		return s.acceptInvFunc(i, o)
   411  	}
   412  
   413  	return "", nil
   414  }
   415  
   416  func (s *stubOOBService) SaveInvitation(i *outofband.Invitation) error {
   417  	if s.saveInvFunc != nil {
   418  		return s.saveInvFunc(i)
   419  	}
   420  
   421  	return nil
   422  }
   423  
   424  func (s *stubOOBService) Actions() ([]outofband.Action, error) {
   425  	if s.actionsFunc != nil {
   426  		return s.actionsFunc()
   427  	}
   428  
   429  	return nil, nil
   430  }
   431  
   432  func (s *stubOOBService) ActionContinue(piid string, opts outofband.Options) error {
   433  	if s.actionContinueFunc != nil {
   434  		return s.actionContinueFunc(piid, opts)
   435  	}
   436  
   437  	return nil
   438  }
   439  
   440  func (s *stubOOBService) ActionStop(piid string, err error) error {
   441  	if s.actionStopFunc != nil {
   442  		return s.actionStopFunc(piid, err)
   443  	}
   444  
   445  	return nil
   446  }