github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/tequilapi/endpoints/proposals_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 "errors" 22 "fmt" 23 "math/big" 24 "net/http" 25 "net/http/httptest" 26 "net/url" 27 "testing" 28 29 "github.com/gin-gonic/gin" 30 "github.com/stretchr/testify/assert" 31 "golang.org/x/net/context" 32 33 "github.com/mysteriumnetwork/node/core/discovery/proposal" 34 "github.com/mysteriumnetwork/node/core/location/locationstate" 35 "github.com/mysteriumnetwork/node/market" 36 "github.com/mysteriumnetwork/node/mocks" 37 "github.com/mysteriumnetwork/node/nat" 38 ) 39 40 var TestLocation = market.Location{ASN: 123, Country: "Lithuania", City: "Vilnius"} 41 42 var ( 43 priceHourMax = big.NewInt(50000) 44 priceGiBMax = big.NewInt(7000000) 45 mockQuality = mocks.Quality() 46 ) 47 48 var serviceProposals = []proposal.PricedServiceProposal{ 49 { 50 ServiceProposal: market.NewProposal("0xProviderId", "testprotocol", market.NewProposalOpts{ 51 Location: &TestLocation, 52 Quality: &mockQuality, 53 }), 54 Price: market.Price{ 55 PricePerHour: big.NewInt(500_000_000_000_000_000), 56 PricePerGiB: big.NewInt(1_000_000_000_000_000_000), 57 }, 58 }, 59 { 60 ServiceProposal: market.NewProposal("other_provider", "testprotocol", market.NewProposalOpts{ 61 Location: &TestLocation, 62 Quality: &mockQuality, 63 }), 64 Price: market.Price{ 65 PricePerHour: big.NewInt(500_000_000_000_000_000), 66 PricePerGiB: big.NewInt(1_000_000_000_000_000_000), 67 }, 68 }, 69 } 70 71 type mockNATProber struct { 72 returnRes nat.NATType 73 returnErr error 74 } 75 76 func (m *mockNATProber) Probe(_ context.Context) (nat.NATType, error) { 77 return m.returnRes, m.returnErr 78 } 79 80 var mockedNATProber = &mockNATProber{"none", nil} 81 82 type mockResolver struct{} 83 84 func (r *mockResolver) DetectLocation() (locationstate.Location, error) { 85 return locationstate.Location{}, nil 86 } 87 88 func (r *mockResolver) DetectProxyLocation(_ int) (locationstate.Location, error) { 89 return r.DetectLocation() 90 } 91 92 type mockPricer struct { 93 priceToReturn market.Price 94 } 95 96 func (mpip *mockPricer) GetCurrentPrice(nodeType string, country string, serviceType string) (market.Price, error) { 97 return mpip.priceToReturn, nil 98 } 99 100 func TestProposalsEndpointListByNodeId(t *testing.T) { 101 repository := &mockProposalRepository{ 102 // we assume that underling component does correct filtering 103 proposals: []proposal.PricedServiceProposal{serviceProposals[0]}, 104 } 105 106 path := "/proposals" 107 req, err := http.NewRequest( 108 http.MethodGet, 109 path, 110 nil, 111 ) 112 assert.Nil(t, err) 113 114 query := req.URL.Query() 115 query.Set("provider_id", "0xProviderId") 116 req.URL.RawQuery = query.Encode() 117 118 resp := httptest.NewRecorder() 119 endpoint := NewProposalsEndpoint(repository, nil, nil, &mockFilterPresetRepository{}, mockedNATProber) 120 g := gin.Default() 121 g.GET(path, endpoint.List) 122 g.ServeHTTP(resp, req) 123 124 assert.JSONEq( 125 t, 126 `{ 127 "proposals": [ 128 { 129 "format": "service-proposal/v3", 130 "compatibility": 2, 131 "provider_id": "0xProviderId", 132 "service_type": "testprotocol", 133 "location": { 134 "asn": 123, 135 "country": "Lithuania", 136 "city": "Vilnius" 137 }, 138 "quality": { 139 "quality": 2.0, 140 "latency": 50, 141 "bandwidth": 10, 142 "uptime": 20 143 }, 144 "price": { 145 "currency": "MYST", 146 "per_gib": 1000000000000000000, 147 "per_gib_tokens": { 148 "ether": "1", 149 "human": "1", 150 "wei": "1000000000000000000" 151 }, 152 "per_hour": 500000000000000000, 153 "per_hour_tokens": { 154 "ether": "0.5", 155 "human": "0.5", 156 "wei": "500000000000000000" 157 } 158 } 159 } 160 ] 161 }`, 162 resp.Body.String(), 163 ) 164 165 assert.EqualValues(t, &proposal.Filter{ 166 ProviderID: "0xProviderId", 167 ExcludeUnsupported: true, 168 CompatibilityMin: 2, 169 }, repository.recordedFilter) 170 } 171 172 func TestProposalsEndpointAcceptsAccessPolicyParams(t *testing.T) { 173 repository := &mockProposalRepository{ 174 proposals: []proposal.PricedServiceProposal{serviceProposals[0]}, 175 } 176 path := "/proposals" 177 req, err := http.NewRequest( 178 http.MethodGet, 179 path, 180 nil, 181 ) 182 assert.Nil(t, err) 183 184 query := req.URL.Query() 185 query.Set("access_policy", "accessPolicy") 186 query.Set("access_policy_source", "accessPolicySource") 187 req.URL.RawQuery = query.Encode() 188 189 resp := httptest.NewRecorder() 190 endpoint := NewProposalsEndpoint(repository, nil, nil, &mockFilterPresetRepository{}, mockedNATProber) 191 192 g := gin.Default() 193 g.GET(path, endpoint.List) 194 g.ServeHTTP(resp, req) 195 196 assert.JSONEq( 197 t, 198 `{ 199 "proposals": [ 200 { 201 "format": "service-proposal/v3", 202 "compatibility": 2, 203 "provider_id": "0xProviderId", 204 "service_type": "testprotocol", 205 "location": { 206 "asn": 123, 207 "country": "Lithuania", 208 "city": "Vilnius" 209 }, 210 "quality": { 211 "quality": 2.0, 212 "latency": 50, 213 "bandwidth": 10, 214 "uptime": 20 215 }, 216 "price": { 217 "currency": "MYST", 218 "per_gib": 1000000000000000000, 219 "per_gib_tokens": { 220 "ether": "1", 221 "human": "1", 222 "wei": "1000000000000000000" 223 }, 224 "per_hour": 500000000000000000, 225 "per_hour_tokens": { 226 "ether": "0.5", 227 "human": "0.5", 228 "wei": "500000000000000000" 229 } 230 } 231 } 232 ] 233 }`, 234 resp.Body.String(), 235 ) 236 assert.Equal(t, 237 &proposal.Filter{ 238 AccessPolicy: "accessPolicy", 239 AccessPolicySource: "accessPolicySource", 240 ExcludeUnsupported: true, 241 CompatibilityMin: 2, 242 }, 243 repository.recordedFilter, 244 ) 245 } 246 247 func TestCurrentPrices(t *testing.T) { 248 // given 249 repository := &mockProposalRepository{ 250 proposals: serviceProposals, 251 } 252 presetRepository := &mockFilterPresetRepository{ 253 presets: proposal.FilterPresets{Entries: []proposal.FilterPreset{ 254 { 255 ID: 0, 256 Name: "", 257 IPType: "", 258 }, 259 }}, 260 } 261 endpoint := NewProposalsEndpoint(repository, &mockPricer{ 262 priceToReturn: market.Price{ 263 PricePerHour: big.NewInt(123_000_000_000_000_000), 264 PricePerGiB: big.NewInt(456_000_000_000_000_000), 265 }, 266 }, &mockResolver{}, presetRepository, mockedNATProber) 267 268 path := "/prices/current" 269 req, err := http.NewRequest( 270 http.MethodGet, 271 path, 272 nil, 273 ) 274 assert.Nil(t, err) 275 276 g := gin.Default() 277 g.GET(path, endpoint.CurrentPrice) 278 resp := httptest.NewRecorder() 279 g.ServeHTTP(resp, req) 280 281 assert.JSONEq( 282 t, 283 ` 284 { 285 "service_type": "wireguard", 286 "price_per_hour": 123000000000000000, 287 "price_per_hour_tokens": { 288 "wei": "123000000000000000", 289 "ether": "0.123", 290 "human": "0.123" 291 }, 292 "price_per_gib": 456000000000000000, 293 "price_per_gib_tokens": { 294 "wei": "456000000000000000", 295 "ether": "0.456", 296 "human": "0.456" 297 } 298 } 299 `, 300 resp.Body.String(), 301 ) 302 } 303 304 func TestProposalsEndpointFilterByPresetID(t *testing.T) { 305 repository := &mockProposalRepository{ 306 proposals: serviceProposals, 307 } 308 309 path := "/proposals" 310 req, err := http.NewRequest( 311 http.MethodGet, 312 path, 313 nil, 314 ) 315 assert.Nil(t, err) 316 317 resp := httptest.NewRecorder() 318 presetRepository := &mockFilterPresetRepository{ 319 presets: proposal.FilterPresets{Entries: []proposal.FilterPreset{ 320 { 321 ID: 0, 322 Name: "", 323 IPType: "", 324 }, 325 }}, 326 } 327 endpoint := NewProposalsEndpoint(repository, nil, nil, presetRepository, mockedNATProber) 328 g := gin.Default() 329 g.GET(path, endpoint.List) 330 g.ServeHTTP(resp, req) 331 332 assert.JSONEq( 333 t, 334 `{ 335 "proposals": [ 336 { 337 "format": "service-proposal/v3", 338 "compatibility": 2, 339 "provider_id": "0xProviderId", 340 "service_type": "testprotocol", 341 "location": { 342 "asn": 123, 343 "country": "Lithuania", 344 "city": "Vilnius" 345 }, 346 "quality": { 347 "quality": 2.0, 348 "latency": 50, 349 "bandwidth": 10, 350 "uptime": 20 351 }, 352 "price": { 353 "currency": "MYST", 354 "per_gib": 1000000000000000000, 355 "per_gib_tokens": { 356 "ether": "1", 357 "human": "1", 358 "wei": "1000000000000000000" 359 }, 360 "per_hour": 500000000000000000, 361 "per_hour_tokens": { 362 "ether": "0.5", 363 "human": "0.5", 364 "wei": "500000000000000000" 365 } 366 } 367 }, 368 { 369 "format": "service-proposal/v3", 370 "compatibility": 2, 371 "provider_id": "other_provider", 372 "service_type": "testprotocol", 373 "location": { 374 "asn": 123, 375 "country": "Lithuania", 376 "city": "Vilnius" 377 }, 378 "quality": { 379 "quality": 2.0, 380 "latency": 50, 381 "bandwidth": 10, 382 "uptime": 20 383 }, 384 "price": { 385 "currency": "MYST", 386 "per_gib": 1000000000000000000, 387 "per_gib_tokens": { 388 "ether": "1", 389 "human": "1", 390 "wei": "1000000000000000000" 391 }, 392 "per_hour": 500000000000000000, 393 "per_hour_tokens": { 394 "ether": "0.5", 395 "human": "0.5", 396 "wei": "500000000000000000" 397 } 398 } 399 } 400 ] 401 }`, 402 resp.Body.String(), 403 ) 404 } 405 406 type mockProposalRepository struct { 407 proposals []proposal.PricedServiceProposal 408 recordedFilter *proposal.Filter 409 priceToAdd market.Price 410 } 411 412 func (m *mockProposalRepository) Proposal(_ market.ProposalID) (*proposal.PricedServiceProposal, error) { 413 if len(m.proposals) == 0 { 414 return nil, nil 415 } 416 return &m.proposals[0], nil 417 } 418 419 func (m *mockProposalRepository) Proposals(filter *proposal.Filter) ([]proposal.PricedServiceProposal, error) { 420 m.recordedFilter = filter 421 return m.proposals, nil 422 } 423 424 func (m *mockProposalRepository) Countries(filter *proposal.Filter) (map[string]int, error) { 425 m.recordedFilter = filter 426 return nil, nil 427 } 428 429 func (m *mockProposalRepository) EnrichProposalWithPrice(in market.ServiceProposal) (proposal.PricedServiceProposal, error) { 430 return proposal.PricedServiceProposal{ 431 Price: m.priceToAdd, 432 ServiceProposal: in, 433 }, nil 434 } 435 436 type mockFilterPresetRepository struct { 437 presets proposal.FilterPresets 438 } 439 440 func (m *mockFilterPresetRepository) List() (*proposal.FilterPresets, error) { 441 return &m.presets, nil 442 } 443 444 func (m *mockFilterPresetRepository) Get(id int) (*proposal.FilterPreset, error) { 445 for _, p := range m.presets.Entries { 446 if p.ID == id { 447 return &p, nil 448 } 449 } 450 return nil, errors.New("preset not found") 451 } 452 453 func setPricingBounds(v url.Values) { 454 v.Add("price_hour_max", fmt.Sprintf("%v", priceHourMax)) 455 v.Add("price_gib_max", fmt.Sprintf("%v", priceGiBMax)) 456 }