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 }