Skip to content

Commit

Permalink
feat(SPV-1295): testabilities for contacts admin (#842)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzolt-4chain authored Jan 9, 2025
1 parent a4674ce commit b3d25d9
Show file tree
Hide file tree
Showing 10 changed files with 376 additions and 178 deletions.
4 changes: 4 additions & 0 deletions engine/action_contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ func (c *Client) UpdateContact(ctx context.Context, id, fullName string, metadat

// AdminCreateContact creates a new contact - xpubId is retrieved by the creatorPaymail.
func (c *Client) AdminCreateContact(ctx context.Context, contactPaymail, creatorPaymail, fullName string, metadata *Metadata) (*Contact, error) {
if contactPaymail == "" {
return nil, spverrors.ErrMissingContactPaymailParam
}

if err := validateNewContactReqFields(fullName, creatorPaymail); err != nil {
return nil, err
}
Expand Down
177 changes: 177 additions & 0 deletions engine/contact/contact_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package contact_test

import (
"context"
"testing"

"github.com/bitcoin-sv/go-paymail"
"github.com/bitcoin-sv/spv-wallet/engine"
"github.com/bitcoin-sv/spv-wallet/engine/contact/testabilities"
"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
"github.com/bitcoin-sv/spv-wallet/engine/tester/fixtures"
)

func Test_ClientService_AdminCreateContact_Success(t *testing.T) {
tests := map[string]struct {
contactPaymail string
creatorPaymail string
fullName string
metadata *engine.Metadata
}{
"Create contact without metadata": {
contactPaymail: fixtures.RecipientExternal.DefaultPaymail(),
creatorPaymail: fixtures.Sender.DefaultPaymail(),
fullName: "John Doe",
metadata: nil,
},
"Create contact with metadata": {
contactPaymail: fixtures.RecipientExternal.DefaultPaymail(),
creatorPaymail: fixtures.Sender.DefaultPaymail(),
fullName: "John Doe",
metadata: &engine.Metadata{
"key1": "value1",
"key2": 420,
},
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
//given:
given, then := testabilities.New(t)

service, cleanup := given.Engine()
defer cleanup()

//when:
contact, err := service.AdminCreateContact(context.Background(),
tt.contactPaymail,
tt.creatorPaymail,
tt.fullName,
tt.metadata,
)

//then:
then.
Created(contact).
WithNoError(err).
ForUser(fixtures.Sender).
ToCounterparty(fixtures.RecipientExternal).
AsNotConfirmed().
WithFullName(tt.fullName)
})
}
}

func Test_ClientService_AdminCreateContact_PKIRetrievalFail(t *testing.T) {
t.Run("Should fail with PKI retrieval", func(t *testing.T) {
//given:
given, then := testabilities.New(t)

service, cleanup := given.Engine()
defer cleanup()

//and:
given.
ExternalPaymailServer().
WillRespondOnCapability(paymail.BRFCPki).
WithInternalServerError()

//when:
contact, err := service.AdminCreateContact(context.Background(),
fixtures.RecipientExternal.DefaultPaymail(),
fixtures.Sender.DefaultPaymail(),
"John Doe",
nil,
)

//then:
then.Created(contact).WithError(err).ThatIs(spverrors.ErrGettingPKIFailed)
})
}

func Test_ClientService_AdminCreateContact_Fail(t *testing.T) {
tests := map[string]struct {
contactPaymail string
creatorPaymail string
fullName string
expectedError error
}{
"Should fail when creator paymail not found": {
contactPaymail: fixtures.RecipientExternal.DefaultPaymail(),
creatorPaymail: "not_exist@example.com",
fullName: "John Doe",
expectedError: spverrors.ErrCouldNotFindPaymail,
},
"Should fail when missing creator paymail": {
contactPaymail: fixtures.RecipientExternal.DefaultPaymail(),
creatorPaymail: "",
fullName: "John Doe",
expectedError: spverrors.ErrMissingContactCreatorPaymail,
},
"Should fail when missing contact full name": {
contactPaymail: fixtures.RecipientExternal.DefaultPaymail(),
creatorPaymail: fixtures.Sender.DefaultPaymail(),
fullName: "",
expectedError: spverrors.ErrMissingContactFullName,
},
"Should fail when missing contact paymail": {
contactPaymail: "",
creatorPaymail: fixtures.Sender.DefaultPaymail(),
fullName: "John Doe",
expectedError: spverrors.ErrMissingContactPaymailParam,
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
//given:
given, then := testabilities.New(t)

service, cleanup := given.Engine()
defer cleanup()

//when:
contact, err := service.AdminCreateContact(context.Background(),
tt.contactPaymail,
tt.creatorPaymail,
tt.fullName,
nil,
)

//then:
then.Created(contact).WithError(err).ThatIs(tt.expectedError)
})
}

}

func Test_ClientService_AdminCreateContact_ContactAlreadyExists(t *testing.T) {
t.Run("Should fail the second time due to contact already exists", func(t *testing.T) {
//given:
given, then := testabilities.New(t)

service, cleanup := given.Engine()
defer cleanup()

//and:
contact, err := service.AdminCreateContact(context.Background(),
fixtures.RecipientExternal.DefaultPaymail(),
fixtures.Sender.DefaultPaymail(),
"John Doe",
nil,
)
then.Created(contact).WithNoError(err)

//when:
contact, err = service.AdminCreateContact(context.Background(),
fixtures.RecipientExternal.DefaultPaymail(),
fixtures.Sender.DefaultPaymail(),
"John Doe",
nil,
)

//then:
then.Created(contact).WithError(err).ThatIs(spverrors.ErrContactAlreadyExists)
})
}
7 changes: 7 additions & 0 deletions engine/contact/testabilities/ability_contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package testabilities

import "testing"

func New(t testing.TB) (ContactFixture, ContactAssertion) {
return given(t), then(t)
}
99 changes: 99 additions & 0 deletions engine/contact/testabilities/assert_contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package testabilities

import (
"testing"

"github.com/bitcoin-sv/spv-wallet/engine"
"github.com/bitcoin-sv/spv-wallet/engine/tester/fixtures"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type assertion struct {
t testing.TB
require *require.Assertions
assert *assert.Assertions
err error
contact *engine.Contact
}

type ContactFailureAssertion interface {
ThatIs(expectedErr error) ContactFailureAssertion
}

type ContactSuccessAssertion interface {
WithStatus(status engine.ContactStatus) ContactSuccessAssertion
AsNotConfirmed() ContactSuccessAssertion
WithFullName(fullName string) ContactSuccessAssertion
ForUser(user fixtures.User) ContactSuccessAssertion
ToCounterparty(user fixtures.User) ContactSuccessAssertion
WithPubKey(pki string) ContactSuccessAssertion
}

type ContactErrorAssertion interface {
WithError(err error) ContactFailureAssertion
WithNoError(err error) ContactSuccessAssertion
}
type ContactAssertion interface {
Created(contact *engine.Contact) ContactErrorAssertion
}

func then(t testing.TB) ContactAssertion {
return &assertion{
t: t,
require: require.New(t),
assert: assert.New(t),
}
}

func (a *assertion) WithError(err error) ContactFailureAssertion {
a.require.Nil(a.contact, "unexpected response")
a.require.Error(err, "error expected")
a.err = err
return a
}

func (a *assertion) WithNoError(err error) ContactSuccessAssertion {
a.require.NotNil(a.contact, "unexpected nil response")
a.require.NoError(err, "unexpected error on contact creation")
return a
}

func (a *assertion) ThatIs(expectedError error) ContactFailureAssertion {
a.require.ErrorIs(a.err, expectedError)
return a
}

func (a *assertion) Created(contact *engine.Contact) ContactErrorAssertion {
a.contact = contact
return a
}

func (a *assertion) AsNotConfirmed() ContactSuccessAssertion {
return a.WithStatus(engine.ContactNotConfirmed)
}

func (a *assertion) WithStatus(status engine.ContactStatus) ContactSuccessAssertion {
a.assert.Equal(status, a.contact.Status)
return a
}

func (a *assertion) WithFullName(fullName string) ContactSuccessAssertion {
a.assert.Equal(fullName, a.contact.FullName)
return a
}

func (a *assertion) WithPubKey(pki string) ContactSuccessAssertion {
a.assert.Equal(pki, a.contact.PubKey, "counterparty pki invalid")
return a
}

func (a *assertion) ForUser(user fixtures.User) ContactSuccessAssertion {
a.assert.Equal(user.XPubID(), a.contact.OwnerXpubID, "contact owner xpub id invalid")
return a
}

func (a *assertion) ToCounterparty(user fixtures.User) ContactSuccessAssertion {
a.assert.Equal(user.DefaultPaymail(), a.contact.Paymail, "counterparty paymail invalid")
return a
}
36 changes: 36 additions & 0 deletions engine/contact/testabilities/fixture_contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package testabilities

import (
"testing"

"github.com/bitcoin-sv/spv-wallet/engine"
testengine "github.com/bitcoin-sv/spv-wallet/engine/testabilities"
"github.com/bitcoin-sv/spv-wallet/engine/tester/paymailmock"
)

type contactFixture struct {
engineFixture testengine.EngineFixture
t testing.TB
}

type ContactFixture interface {
Engine() (engine.ClientInterface, func())
ExternalPaymailServer() *paymailmock.PaymailClientMock
}

func given(t testing.TB) ContactFixture {
return &contactFixture{
t: t,
engineFixture: testengine.Given(t),
}
}

func (cf *contactFixture) Engine() (engine.ClientInterface, func()) {
engine, cleanup := cf.engineFixture.Engine()

return engine.Engine, cleanup
}

func (cf *contactFixture) ExternalPaymailServer() *paymailmock.PaymailClientMock {
return cf.engineFixture.PaymailClient()
}
Loading

0 comments on commit b3d25d9

Please sign in to comment.