github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/tequilapi/endpoints/identities_test.go (about)

     1  /*
     2   * Copyright (C) 2017 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package endpoints
    19  
    20  import (
    21  	"bytes"
    22  	"fmt"
    23  	"math/big"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"testing"
    27  
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/gin-gonic/gin"
    30  	"github.com/mysteriumnetwork/go-rest/apierror"
    31  	"github.com/mysteriumnetwork/node/identity"
    32  	"github.com/mysteriumnetwork/node/identity/registry"
    33  	"github.com/mysteriumnetwork/node/session/pingpong"
    34  	pingpongEvent "github.com/mysteriumnetwork/node/session/pingpong/event"
    35  	"github.com/mysteriumnetwork/payments/client"
    36  	"github.com/stretchr/testify/assert"
    37  )
    38  
    39  const identityUrl = "/irrelevant"
    40  
    41  var (
    42  	existingIdentities = []identity.Identity{
    43  		{Address: "0x000000000000000000000000000000000000000a"},
    44  		{Address: "0x000000000000000000000000000000000000beef"},
    45  	}
    46  	newIdentity = identity.Identity{Address: "0x000000000000000000000000000000000000aaac"}
    47  )
    48  
    49  type mockBeneficiaryProvider struct {
    50  	b common.Address
    51  }
    52  
    53  func (ms *mockBeneficiaryProvider) GetBeneficiary(identity common.Address) (common.Address, error) {
    54  	return ms.b, nil
    55  }
    56  
    57  type selectorFake struct {
    58  }
    59  
    60  func summonTestGin() *gin.Engine {
    61  	g := gin.Default()
    62  	g.Use(apierror.ErrorHandler)
    63  	return g
    64  }
    65  
    66  func (hf *selectorFake) UseOrCreate(address, _ string, _ int64) (identity.Identity, error) {
    67  	if len(address) > 0 {
    68  		return identity.Identity{Address: address}, nil
    69  	}
    70  
    71  	return identity.Identity{Address: "0x000000"}, nil
    72  }
    73  
    74  func (hf *selectorFake) SetDefault(address string) error {
    75  	return nil
    76  }
    77  
    78  func TestCurrentIdentitySuccess(t *testing.T) {
    79  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
    80  	resp := httptest.NewRecorder()
    81  	req, err := http.NewRequest(
    82  		http.MethodPut,
    83  		"/identities/current",
    84  		bytes.NewBufferString(`{"passphrase": "mypassphrase"}`),
    85  	)
    86  	assert.Nil(t, err)
    87  
    88  	endpoint := &identitiesAPI{
    89  		idm:      mockIdm,
    90  		selector: &selectorFake{},
    91  	}
    92  
    93  	g := summonTestGin()
    94  	g.PUT("/identities/current", endpoint.Current)
    95  
    96  	g.ServeHTTP(resp, req)
    97  
    98  	assert.Equal(t, http.StatusOK, resp.Code)
    99  	assert.JSONEq(
   100  		t,
   101  		`{
   102  			"id": "0x000000"
   103  		}`,
   104  		resp.Body.String(),
   105  	)
   106  }
   107  
   108  func TestUnlockIdentitySuccess(t *testing.T) {
   109  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
   110  	resp := httptest.NewRecorder()
   111  	req, err := http.NewRequest(
   112  		http.MethodPut,
   113  		fmt.Sprintf("/identities/%s/unlock", "0x000000000000000000000000000000000000000a"),
   114  		bytes.NewBufferString(`{"passphrase": "mypassphrase"}`),
   115  	)
   116  	assert.Nil(t, err)
   117  
   118  	endpoint := &identitiesAPI{idm: mockIdm}
   119  
   120  	g := summonTestGin()
   121  	g.PUT("/identities/:id/unlock", endpoint.Unlock)
   122  
   123  	g.ServeHTTP(resp, req)
   124  
   125  	assert.Equal(t, http.StatusAccepted, resp.Code)
   126  
   127  	assert.Equal(t, "0x000000000000000000000000000000000000000a", mockIdm.LastUnlockAddress)
   128  	assert.Equal(t, "mypassphrase", mockIdm.LastUnlockPassphrase)
   129  	assert.Equal(t, int64(0), mockIdm.LastUnlockChainID)
   130  }
   131  
   132  func TestUnlockIdentityWithInvalidJSON(t *testing.T) {
   133  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
   134  	resp := httptest.NewRecorder()
   135  	req, err := http.NewRequest(
   136  		http.MethodPut,
   137  		fmt.Sprintf("/identities/%s/unlock", "0x000000000000000000000000000000000000000a"),
   138  		bytes.NewBufferString(`{invalid json}`),
   139  	)
   140  	assert.Nil(t, err)
   141  
   142  	endpoint := &identitiesAPI{idm: mockIdm}
   143  	g := summonTestGin()
   144  	g.PUT("/identities/:id/unlock", endpoint.Unlock)
   145  
   146  	g.ServeHTTP(resp, req)
   147  
   148  	assert.Equal(t, http.StatusBadRequest, resp.Code)
   149  }
   150  
   151  func TestUnlockIdentityWithNoPassphrase(t *testing.T) {
   152  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
   153  	resp := httptest.NewRecorder()
   154  	req, err := http.NewRequest(
   155  		http.MethodPut,
   156  		fmt.Sprintf("/identities/%s/unlock", "0x000000000000000000000000000000000000000a"),
   157  		bytes.NewBufferString(`{}`),
   158  	)
   159  	assert.NoError(t, err)
   160  
   161  	endpoint := &identitiesAPI{idm: mockIdm}
   162  	g := summonTestGin()
   163  	g.PUT("/identities/:id/unlock", endpoint.Unlock)
   164  
   165  	g.ServeHTTP(resp, req)
   166  
   167  	assert.Equal(t, http.StatusBadRequest, resp.Code)
   168  	assert.JSONEq(
   169  		t,
   170  		`{
   171    "error": {
   172      "code": "validation_failed",
   173      "message": "Request validation failed",
   174      "detail": "Request validation failed: passphrase: 'passphrase' is required [required]",
   175      "fields": {
   176        "passphrase": {
   177          "code": "required",
   178          "message": "'passphrase' is required"
   179        }
   180      }
   181    },
   182    "status": 400,
   183    "path": "/identities/0x000000000000000000000000000000000000000a/unlock"
   184  }`,
   185  		resp.Body.String(),
   186  	)
   187  }
   188  
   189  func TestBeneficiaryWithChannel(t *testing.T) {
   190  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
   191  	resp := httptest.NewRecorder()
   192  	req, err := http.NewRequest(
   193  		http.MethodGet,
   194  		fmt.Sprintf("/identities/%s/beneficiary", "0x000000000000000000000000000000000000000a"),
   195  		nil,
   196  	)
   197  	assert.Nil(t, err)
   198  
   199  	endpoint := &identitiesAPI{
   200  		idm: mockIdm,
   201  		addressProvider: &mockAddressProvider{
   202  			hermesToReturn:         common.HexToAddress("0x000000000000000000000000000000000000000b"),
   203  			registryToReturn:       common.HexToAddress("0x00000000000000000000000000000000000000cb"),
   204  			channelToReturn:        common.HexToAddress("0x0000000000000000000000000000000000000ddb"),
   205  			channelAddressToReturn: common.HexToAddress("0x0000000000000000000000000000000000001234"),
   206  		},
   207  		bprovider: &mockBeneficiaryProvider{
   208  			b: common.HexToAddress("0x0000000000000000000000000000000000001234"),
   209  		},
   210  	}
   211  	g := summonTestGin()
   212  	g.GET("/identities/:id/beneficiary", endpoint.Beneficiary)
   213  
   214  	g.ServeHTTP(resp, req)
   215  
   216  	assert.Equal(t, http.StatusOK, resp.Code)
   217  	assert.JSONEq(
   218  		t,
   219  		`{
   220  			"beneficiary":"0x0000000000000000000000000000000000001234",
   221  			"is_channel_address":true
   222    		}`,
   223  		resp.Body.String(),
   224  	)
   225  }
   226  
   227  func TestBeneficiaryWithoutChannel(t *testing.T) {
   228  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
   229  	resp := httptest.NewRecorder()
   230  	req, err := http.NewRequest(
   231  		http.MethodGet,
   232  		fmt.Sprintf("/identities/%s/beneficiary", "0x000000000000000000000000000000000000000a"),
   233  		nil,
   234  	)
   235  	assert.Nil(t, err)
   236  
   237  	endpoint := &identitiesAPI{
   238  		idm: mockIdm,
   239  		addressProvider: &mockAddressProvider{
   240  			hermesToReturn:         common.HexToAddress("0x000000000000000000000000000000000000000b"),
   241  			registryToReturn:       common.HexToAddress("0x00000000000000000000000000000000000000cb"),
   242  			channelToReturn:        common.HexToAddress("0x0000000000000000000000000000000000000ddb"),
   243  			channelAddressToReturn: common.HexToAddress("0x000000000000000000000000000000000000eeeb"),
   244  		},
   245  		bprovider: &mockBeneficiaryProvider{
   246  			b: common.HexToAddress("0x0000000000000000000000000000000000000123"),
   247  		},
   248  	}
   249  	g := summonTestGin()
   250  	g.GET("/identities/:id/beneficiary", endpoint.Beneficiary)
   251  
   252  	g.ServeHTTP(resp, req)
   253  
   254  	assert.Equal(t, http.StatusOK, resp.Code)
   255  	assert.JSONEq(
   256  		t,
   257  		`{
   258  			"beneficiary":"0x0000000000000000000000000000000000000123",
   259  			"is_channel_address":false
   260    		}`,
   261  		resp.Body.String(),
   262  	)
   263  }
   264  
   265  func TestUnlockFailure(t *testing.T) {
   266  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
   267  	resp := httptest.NewRecorder()
   268  	req, err := http.NewRequest(
   269  		http.MethodPut,
   270  		fmt.Sprintf("/identities/%s/unlock", "0x000000000000000000000000000000000000000a"),
   271  		bytes.NewBufferString(`{"passphrase": "mypassphrase"}`),
   272  	)
   273  	assert.Nil(t, err)
   274  
   275  	mockIdm.MarkUnlockToFail()
   276  
   277  	endpoint := &identitiesAPI{idm: mockIdm}
   278  	g := summonTestGin()
   279  	g.PUT("/identities/:id/unlock", endpoint.Unlock)
   280  
   281  	g.ServeHTTP(resp, req)
   282  
   283  	assert.Equal(t, http.StatusForbidden, resp.Code)
   284  
   285  	assert.Equal(t, "0x000000000000000000000000000000000000000a", mockIdm.LastUnlockAddress)
   286  	assert.Equal(t, "mypassphrase", mockIdm.LastUnlockPassphrase)
   287  	assert.Equal(t, int64(0), mockIdm.LastUnlockChainID)
   288  }
   289  
   290  func TestCreateNewIdentityEmptyPassphrase(t *testing.T) {
   291  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
   292  	resp := httptest.NewRecorder()
   293  	req, err := http.NewRequest(
   294  		http.MethodPost,
   295  		"/identities",
   296  		bytes.NewBufferString(`{"passphrase": ""}`),
   297  	)
   298  	assert.Nil(t, err)
   299  
   300  	endpoint := &identitiesAPI{idm: mockIdm}
   301  	g := summonTestGin()
   302  	g.POST("/identities", endpoint.Create)
   303  
   304  	g.ServeHTTP(resp, req)
   305  
   306  	assert.Equal(t, http.StatusOK, resp.Code)
   307  }
   308  
   309  func TestCreateNewIdentityNoPassphrase(t *testing.T) {
   310  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
   311  	resp := httptest.NewRecorder()
   312  	req, err := http.NewRequest(
   313  		http.MethodPost,
   314  		"/identities",
   315  		bytes.NewBufferString(`{}`),
   316  	)
   317  	assert.Nil(t, err)
   318  
   319  	endpoint := &identitiesAPI{idm: mockIdm}
   320  	g := summonTestGin()
   321  	g.POST("/identities", endpoint.Create)
   322  
   323  	g.ServeHTTP(resp, req)
   324  
   325  	fmt.Println(resp.Body.String())
   326  	assert.Equal(t, http.StatusBadRequest, resp.Code)
   327  	assert.JSONEq(
   328  		t,
   329  		`{
   330    "error": {
   331      "code": "validation_failed",
   332      "message": "Request validation failed",
   333      "detail": "Request validation failed: passphrase: 'passphrase' is required [required]",
   334      "fields": {
   335        "passphrase": {
   336          "code": "required",
   337          "message": "'passphrase' is required"
   338        }
   339      }
   340    },
   341    "status": 400,
   342    "path": "/identities"
   343  }`,
   344  		resp.Body.String(),
   345  	)
   346  }
   347  
   348  func TestCreateNewIdentity(t *testing.T) {
   349  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
   350  	resp := httptest.NewRecorder()
   351  	req, err := http.NewRequest(
   352  		http.MethodPost,
   353  		"/identities",
   354  		bytes.NewBufferString(`{"passphrase": "mypass"}`),
   355  	)
   356  	assert.Nil(t, err)
   357  
   358  	endpoint := &identitiesAPI{idm: mockIdm}
   359  	g := summonTestGin()
   360  	g.POST("/identities", endpoint.Create)
   361  
   362  	g.ServeHTTP(resp, req)
   363  
   364  	assert.JSONEq(
   365  		t,
   366  		`{
   367              "id": "0x000000000000000000000000000000000000aaac"
   368          }`,
   369  		resp.Body.String(),
   370  	)
   371  }
   372  
   373  func TestListIdentities(t *testing.T) {
   374  	mockIdm := identity.NewIdentityManagerFake(existingIdentities, newIdentity)
   375  	path := "/identities"
   376  	req := httptest.NewRequest("GET", path, nil)
   377  	resp := httptest.NewRecorder()
   378  
   379  	endpoint := &identitiesAPI{idm: mockIdm}
   380  	g := summonTestGin()
   381  	g.GET(path, endpoint.List)
   382  
   383  	g.ServeHTTP(resp, req)
   384  
   385  	assert.JSONEq(
   386  		t,
   387  		`{
   388              "identities": [
   389                  {"id": "0x000000000000000000000000000000000000000a"},
   390                  {"id": "0x000000000000000000000000000000000000beef"}
   391              ]
   392          }`,
   393  		resp.Body.String(),
   394  	)
   395  }
   396  
   397  func Test_IdentityGet(t *testing.T) {
   398  	endpoint := &identitiesAPI{
   399  		idm:      identity.NewIdentityManagerFake(existingIdentities, newIdentity),
   400  		registry: &registry.FakeRegistry{RegistrationStatus: registry.Registered},
   401  		addressProvider: &mockAddressProvider{
   402  			channelAddressToReturn: common.HexToAddress("0x100000000000000000000000000000000000000a"),
   403  			hermesToReturn:         common.HexToAddress("0x200000000000000000000000000000000000000a"),
   404  		},
   405  		bc: &mockProviderChannelStatusProvider{
   406  			channelToReturn: client.ProviderChannel{
   407  				Settled:       big.NewInt(1),
   408  				Stake:         big.NewInt(2),
   409  				LastUsedNonce: big.NewInt(3),
   410  				Timelock:      big.NewInt(4),
   411  			},
   412  		},
   413  		earningsProvider: &mockEarningsProvider{
   414  			earnings: pingpongEvent.EarningsDetailed{
   415  				Total: pingpongEvent.Earnings{
   416  					LifetimeBalance:  big.NewInt(100),
   417  					UnsettledBalance: big.NewInt(50),
   418  				},
   419  				PerHermes: map[common.Address]pingpongEvent.Earnings{
   420  					common.HexToAddress("0x200000000000000000000000000000000000000a"): {
   421  						LifetimeBalance:  big.NewInt(100),
   422  						UnsettledBalance: big.NewInt(50),
   423  					},
   424  				},
   425  			},
   426  		},
   427  		balanceProvider: &mockBalanceProvider{
   428  			balance: big.NewInt(25),
   429  		},
   430  	}
   431  
   432  	router := gin.Default()
   433  	router.GET("/identities/:id", endpoint.Get)
   434  
   435  	req, err := http.NewRequest(
   436  		http.MethodGet,
   437  		"/identities/0x000000000000000000000000000000000000000a",
   438  		nil,
   439  	)
   440  	assert.Nil(t, err)
   441  
   442  	resp := httptest.NewRecorder()
   443  	router.ServeHTTP(resp, req)
   444  	assert.Equal(t, http.StatusOK, resp.Code)
   445  
   446  	assert.JSONEq(t,
   447  		`
   448  {
   449  	"id": "0x000000000000000000000000000000000000000a",
   450  	"registration_status": "Registered",
   451  	"channel_address": "0x100000000000000000000000000000000000000A",
   452  	"balance": 25,
   453  	"earnings": 50,
   454  	"earnings_total": 100,
   455  	"balance_tokens": {
   456  		"wei": "25",
   457  		"ether": "0.000000000000000025",
   458  		"human": "0"
   459  	},
   460  	"earnings_tokens": {
   461  		"wei": "50",
   462  		"ether": "0.00000000000000005",
   463  		"human": "0"
   464  	},
   465  	"earnings_total_tokens": {
   466  		"wei": "100",
   467  		"ether": "0.0000000000000001",
   468  		"human": "0"
   469  	},
   470  	"stake": 2,
   471  	"hermes_id": "0x200000000000000000000000000000000000000A",
   472  	"earnings_per_hermes": {
   473  		"0x200000000000000000000000000000000000000A": {
   474  			"earnings": {
   475  				"wei": "50",
   476  				"ether": "0.00000000000000005",
   477  				"human": "0"
   478  			},
   479  			"earnings_total": {
   480  				"wei": "100",
   481  				"ether": "0.0000000000000001",
   482  				"human": "0"
   483  			}
   484  		}
   485  	}
   486  }
   487  `,
   488  		resp.Body.String())
   489  }
   490  
   491  type mockAddressProvider struct {
   492  	hermesToReturn         common.Address
   493  	registryToReturn       common.Address
   494  	channelToReturn        common.Address
   495  	channelAddressToReturn common.Address
   496  }
   497  
   498  func (ma *mockAddressProvider) GetActiveChannelImplementation(chainID int64) (common.Address, error) {
   499  	return ma.channelToReturn, nil
   500  }
   501  
   502  func (ma *mockAddressProvider) GetChannelImplementationForHermes(chainID int64, hermes common.Address) (common.Address, error) {
   503  	return ma.channelToReturn, nil
   504  }
   505  
   506  func (ma *mockAddressProvider) GetMystAddress(chainID int64) (common.Address, error) {
   507  	return ma.channelToReturn, nil
   508  }
   509  
   510  func (ma *mockAddressProvider) GetActiveHermes(chainID int64) (common.Address, error) {
   511  	return ma.hermesToReturn, nil
   512  }
   513  
   514  func (ma *mockAddressProvider) GetRegistryAddress(chainID int64) (common.Address, error) {
   515  	return ma.registryToReturn, nil
   516  }
   517  
   518  func (ma *mockAddressProvider) GetActiveChannelAddress(chainID int64, id common.Address) (common.Address, error) {
   519  	return ma.channelAddressToReturn, nil
   520  }
   521  
   522  func (ma *mockAddressProvider) GetHermesChannelAddress(chainID int64, id, hermes common.Address) (common.Address, error) {
   523  	return ma.channelAddressToReturn, nil
   524  }
   525  
   526  func (ma *mockAddressProvider) GetKnownHermeses(chainID int64) ([]common.Address, error) {
   527  	return []common.Address{ma.hermesToReturn}, nil
   528  }
   529  
   530  type mockProviderChannelStatusProvider struct {
   531  	channelToReturn client.ProviderChannel
   532  }
   533  
   534  func (m *mockProviderChannelStatusProvider) GetProviderChannel(chainID int64, hermesAddress common.Address, provider common.Address, pending bool) (client.ProviderChannel, error) {
   535  	return m.channelToReturn, nil
   536  }
   537  
   538  type mockEarningsProvider struct {
   539  	earnings pingpongEvent.EarningsDetailed
   540  	channels []pingpong.HermesChannel
   541  }
   542  
   543  func (mep *mockEarningsProvider) List(chainID int64) []pingpong.HermesChannel {
   544  	return mep.channels
   545  }
   546  
   547  func (mep *mockEarningsProvider) GetEarningsDetailed(chainID int64, _ identity.Identity) *pingpongEvent.EarningsDetailed {
   548  	return &mep.earnings
   549  }
   550  
   551  type mockBalanceProvider struct {
   552  	balance            *big.Int
   553  	forceUpdateBalance *big.Int
   554  }
   555  
   556  func (m *mockBalanceProvider) GetBalance(chainID int64, id identity.Identity) *big.Int {
   557  	return m.balance
   558  }
   559  func (m *mockBalanceProvider) ForceBalanceUpdateCached(chainID int64, id identity.Identity) *big.Int {
   560  	return m.forceUpdateBalance
   561  }