code.vegaprotocol.io/vega@v0.79.0/core/integration/steps/the_activity_streaks_should_be.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package steps
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"strconv"
    22  	"strings"
    23  
    24  	"code.vegaprotocol.io/vega/core/integration/stubs"
    25  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    26  
    27  	"github.com/cucumber/godog"
    28  	"golang.org/x/exp/maps"
    29  	"golang.org/x/exp/slices"
    30  )
    31  
    32  func TheActivityStreaksShouldBe(broker *stubs.BrokerStub, epochStr string, table *godog.Table) error {
    33  	epoch, err := U64(epochStr)
    34  	if err != nil {
    35  		return fmt.Errorf("could not parse epoch: %w", err)
    36  	}
    37  
    38  	expectedActivityStreaksStats, err := parseActivityStreaksShouldBeTable(table)
    39  	if err != nil {
    40  		return fmt.Errorf("table is invalid: %w", err)
    41  	}
    42  
    43  	allStreaks := broker.PartyActivityStreaks()
    44  
    45  	foundStreaksForEpoch := map[string]eventspb.PartyActivityStreak{}
    46  	for _, streak := range allStreaks {
    47  		if streak.Epoch == epoch {
    48  			foundStreaksForEpoch[streak.Party] = streak
    49  		}
    50  	}
    51  
    52  	if len(foundStreaksForEpoch) == 0 && len(expectedActivityStreaksStats) > 0 {
    53  		return fmt.Errorf("no activity streaks found at epoch %v", epochStr)
    54  	}
    55  
    56  	return compareActivityStreaks(expectedActivityStreaksStats, foundStreaksForEpoch)
    57  }
    58  
    59  func parseActivityStreaksShouldBeTable(table *godog.Table) (map[string]eventspb.PartyActivityStreak, error) {
    60  	rows := StrictParseTable(table, []string{
    61  		"party",
    62  		"active for",
    63  		"inactive for",
    64  		"reward multiplier",
    65  		"vesting multiplier",
    66  	}, []string{})
    67  
    68  	stats := map[string]eventspb.PartyActivityStreak{}
    69  	for _, row := range rows {
    70  		specificRow := newActivityStreaksShouldBeRow(row)
    71  		partyID := specificRow.Party()
    72  		_, alreadyRegistered := stats[partyID]
    73  		if alreadyRegistered {
    74  			return nil, fmt.Errorf("cannot have more than one expectation for party %q", partyID)
    75  		}
    76  		stats[partyID] = eventspb.PartyActivityStreak{
    77  			Party:                                partyID,
    78  			ActiveFor:                            specificRow.ActiveFor(),
    79  			InactiveFor:                          specificRow.InactiveFor(),
    80  			RewardDistributionActivityMultiplier: specificRow.RewardMultiplier(),
    81  			RewardVestingActivityMultiplier:      specificRow.VestingMultiplier(),
    82  		}
    83  	}
    84  
    85  	return stats, nil
    86  }
    87  
    88  func compareActivityStreaks(expectedActivityStreaks map[string]eventspb.PartyActivityStreak, foundActivityStreaks map[string]eventspb.PartyActivityStreak) error {
    89  	foundActivityStreaksIDs := maps.Keys(expectedActivityStreaks)
    90  	expectedActivityStreaksIDs := maps.Keys(expectedActivityStreaks)
    91  
    92  	slices.Sort(foundActivityStreaksIDs)
    93  	slices.Sort(expectedActivityStreaksIDs)
    94  
    95  	unexpectedParties := []string{}
    96  	partiesNotFound := []string{}
    97  
    98  	for _, expectedID := range expectedActivityStreaksIDs {
    99  		if _, ok := foundActivityStreaks[expectedID]; !ok {
   100  			partiesNotFound = append(partiesNotFound, expectedID)
   101  		}
   102  	}
   103  
   104  	for _, foundID := range foundActivityStreaksIDs {
   105  		if _, ok := expectedActivityStreaks[foundID]; !ok {
   106  			unexpectedParties = append(unexpectedParties, foundID)
   107  		}
   108  	}
   109  
   110  	var errStr string
   111  	if len(partiesNotFound) > 0 {
   112  		errStr = "parties not found: " + strings.Join(partiesNotFound, ", ")
   113  	}
   114  	if len(unexpectedParties) > 0 {
   115  		if errStr != "" {
   116  			errStr += ", and "
   117  		}
   118  		errStr += "unexpected parties: " + strings.Join(unexpectedParties, ", ")
   119  	}
   120  	if errStr != "" {
   121  		return errors.New(errStr)
   122  	}
   123  
   124  	for _, party := range expectedActivityStreaksIDs {
   125  		foundActivityStreak := foundActivityStreaks[party]
   126  		expectedActivityStreak := expectedActivityStreaks[party]
   127  
   128  		if expectedActivityStreak.ActiveFor != foundActivityStreak.ActiveFor ||
   129  			expectedActivityStreak.InactiveFor != foundActivityStreak.InactiveFor ||
   130  			expectedActivityStreak.RewardDistributionActivityMultiplier != foundActivityStreak.RewardDistributionActivityMultiplier ||
   131  			expectedActivityStreak.RewardVestingActivityMultiplier != foundActivityStreak.RewardVestingActivityMultiplier {
   132  			return formatDiff(
   133  				fmt.Sprintf("activity streak did not match for party %q", party),
   134  				map[string]string{
   135  					"active for":         strconv.FormatUint(expectedActivityStreak.ActiveFor, 10),
   136  					"inactive for":       strconv.FormatUint(expectedActivityStreak.InactiveFor, 10),
   137  					"reward multiplier":  expectedActivityStreak.RewardDistributionActivityMultiplier,
   138  					"vesting multiplier": expectedActivityStreak.RewardVestingActivityMultiplier,
   139  				},
   140  				map[string]string{
   141  					"active for":         strconv.FormatUint(foundActivityStreak.ActiveFor, 10),
   142  					"inactive for":       strconv.FormatUint(foundActivityStreak.InactiveFor, 10),
   143  					"reward multiplier":  foundActivityStreak.RewardDistributionActivityMultiplier,
   144  					"vesting multiplier": foundActivityStreak.RewardVestingActivityMultiplier,
   145  				},
   146  			)
   147  		}
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  type activityStreaksShouldBeRow struct {
   154  	row RowWrapper
   155  }
   156  
   157  func newActivityStreaksShouldBeRow(r RowWrapper) activityStreaksShouldBeRow {
   158  	row := activityStreaksShouldBeRow{
   159  		row: r,
   160  	}
   161  	return row
   162  }
   163  
   164  func (r activityStreaksShouldBeRow) Party() string {
   165  	return r.row.MustStr("party")
   166  }
   167  
   168  func (r activityStreaksShouldBeRow) ActiveFor() uint64 {
   169  	return r.row.MustU64("active for")
   170  }
   171  
   172  func (r activityStreaksShouldBeRow) InactiveFor() uint64 {
   173  	return r.row.MustU64("inactive for")
   174  }
   175  
   176  func (r activityStreaksShouldBeRow) RewardMultiplier() string {
   177  	return r.row.MustStr("reward multiplier")
   178  }
   179  
   180  func (r activityStreaksShouldBeRow) VestingMultiplier() string {
   181  	return r.row.MustStr("vesting multiplier")
   182  }