tutus-chain/pkg/tutustest/events.go

296 lines
8.8 KiB
Go
Executable File

package tutustest
import (
"testing"
"git.marketally.com/tutus-one/tutus-chain/pkg/core/state"
"git.marketally.com/tutus-one/tutus-chain/pkg/util"
"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
// EventMatcher provides fluent event validation for contract tests.
type EventMatcher struct {
t testing.TB
events []state.NotificationEvent
contract util.Uint160
}
// NewEventMatcher creates a matcher for the given events.
func NewEventMatcher(t testing.TB, events []state.NotificationEvent) *EventMatcher {
return &EventMatcher{
t: t,
events: events,
}
}
// FromContract filters events to only those from the specified contract.
func (m *EventMatcher) FromContract(hash util.Uint160) *EventMatcher {
m.contract = hash
return m
}
// HasEvent checks that at least one event with the given name exists.
func (m *EventMatcher) HasEvent(name string) *EventMatcher {
found := false
for _, e := range m.events {
if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
continue
}
if e.Name == name {
found = true
break
}
}
require.True(m.t, found, "expected event %q not found", name)
return m
}
// HasNoEvent checks that no event with the given name exists.
func (m *EventMatcher) HasNoEvent(name string) *EventMatcher {
for _, e := range m.events {
if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
continue
}
require.NotEqual(m.t, name, e.Name, "unexpected event %q found", name)
}
return m
}
// CountEvents returns the number of events with the given name.
func (m *EventMatcher) CountEvents(name string) int {
count := 0
for _, e := range m.events {
if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
continue
}
if e.Name == name {
count++
}
}
return count
}
// RequireEventCount asserts exactly N events with the given name.
func (m *EventMatcher) RequireEventCount(name string, count int) *EventMatcher {
actual := m.CountEvents(name)
require.Equal(m.t, count, actual, "expected %d %q events, got %d", count, name, actual)
return m
}
// GetEvent returns the first event with the given name.
func (m *EventMatcher) GetEvent(name string) *state.NotificationEvent {
for _, e := range m.events {
if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
continue
}
if e.Name == name {
return &e
}
}
return nil
}
// GetEvents returns all events with the given name.
func (m *EventMatcher) GetEvents(name string) []state.NotificationEvent {
var result []state.NotificationEvent
for _, e := range m.events {
if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
continue
}
if e.Name == name {
result = append(result, e)
}
}
return result
}
// EventValidator provides detailed validation of a single event.
type EventValidator struct {
t testing.TB
event *state.NotificationEvent
}
// ValidateEvent creates a validator for a specific event.
func (m *EventMatcher) ValidateEvent(name string) *EventValidator {
event := m.GetEvent(name)
require.NotNil(m.t, event, "event %q not found", name)
return &EventValidator{
t: m.t,
event: event,
}
}
// HasArgs checks that the event has the expected number of arguments.
func (v *EventValidator) HasArgs(count int) *EventValidator {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Equal(v.t, count, len(arr), "expected %d args, got %d", count, len(arr))
return v
}
// ArgEquals checks that argument at index equals the expected value.
func (v *EventValidator) ArgEquals(index int, expected any) *EventValidator {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
actual := arr[index]
exp := stackitem.Make(expected)
require.True(v.t, actual.Equals(exp), "arg[%d]: expected %v, got %v", index, expected, actual)
return v
}
// ArgIsHash160 checks that argument at index is a valid Hash160.
func (v *EventValidator) ArgIsHash160(index int) *EventValidator {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
bs, err := arr[index].TryBytes()
require.NoError(v.t, err, "arg[%d] is not bytes", index)
require.Equal(v.t, 20, len(bs), "arg[%d] is not Hash160 (len=%d)", index, len(bs))
return v
}
// ArgIsPositive checks that argument at index is a positive integer.
func (v *EventValidator) ArgIsPositive(index int) *EventValidator {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
n, err := arr[index].TryInteger()
require.NoError(v.t, err, "arg[%d] is not integer", index)
require.Greater(v.t, n.Int64(), int64(0), "arg[%d] is not positive", index)
return v
}
// ArgIsNonNegative checks that argument at index is a non-negative integer.
func (v *EventValidator) ArgIsNonNegative(index int) *EventValidator {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
n, err := arr[index].TryInteger()
require.NoError(v.t, err, "arg[%d] is not integer", index)
require.GreaterOrEqual(v.t, n.Int64(), int64(0), "arg[%d] is negative", index)
return v
}
// GetArg returns the argument at the given index.
func (v *EventValidator) GetArg(index int) stackitem.Item {
arr, ok := v.event.Item.Value().([]stackitem.Item)
require.True(v.t, ok, "event item is not an array")
require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
return arr[index]
}
// GetArgBytes returns the argument at the given index as bytes.
func (v *EventValidator) GetArgBytes(index int) []byte {
item := v.GetArg(index)
bs, err := item.TryBytes()
require.NoError(v.t, err)
return bs
}
// GetArgInt returns the argument at the given index as int64.
func (v *EventValidator) GetArgInt(index int) int64 {
item := v.GetArg(index)
n, err := item.TryInteger()
require.NoError(v.t, err)
return n.Int64()
}
// GetArgHash160 returns the argument at the given index as Hash160.
func (v *EventValidator) GetArgHash160(index int) util.Uint160 {
bs := v.GetArgBytes(index)
require.Equal(v.t, 20, len(bs))
return util.Uint160(bs)
}
// Common Tutus event names for convenience
const (
// Vita events
EventVitaRegistered = "VitaRegistered"
EventVitaSuspended = "VitaSuspended"
EventVitaRevoked = "VitaRevoked"
// VTS events
EventTransfer = "Transfer"
EventMint = "Mint"
EventBurn = "Burn"
// Eligere events
EventProposalCreated = "ProposalCreated"
EventVoteCast = "VoteCast"
EventProposalPassed = "ProposalPassed"
EventProposalFailed = "ProposalFailed"
// Lex events
EventLawEnacted = "LawEnacted"
EventLawRepealed = "LawRepealed"
EventRightRestricted = "RightRestricted"
EventRightRestored = "RightRestored"
// RoleRegistry events
EventRoleGranted = "RoleGranted"
EventRoleRevoked = "RoleRevoked"
// Scire events
EventEnrollment = "Enrollment"
EventCertification = "Certification"
// Salus events
EventMedicalRecord = "MedicalRecord"
EventEmergencyAccess = "EmergencyAccess"
// Federation events
EventVisitorRegistered = "VisitorRegistered"
EventAsylumGranted = "AsylumGranted"
EventCitizenNaturalized = "CitizenNaturalized"
)
// TransferEventValidator is a specialized validator for Transfer events.
type TransferEventValidator struct {
*EventValidator
}
// ValidateTransfer creates a validator specifically for Transfer events.
func (m *EventMatcher) ValidateTransfer() *TransferEventValidator {
return &TransferEventValidator{
EventValidator: m.ValidateEvent(EventTransfer),
}
}
// From checks the sender of the transfer.
func (v *TransferEventValidator) From(expected util.Uint160) *TransferEventValidator {
v.ArgEquals(0, expected.BytesBE())
return v
}
// To checks the recipient of the transfer.
func (v *TransferEventValidator) To(expected util.Uint160) *TransferEventValidator {
v.ArgEquals(1, expected.BytesBE())
return v
}
// Amount checks the transfer amount.
func (v *TransferEventValidator) Amount(expected int64) *TransferEventValidator {
v.ArgEquals(2, expected)
return v
}
// IsMint checks that this is a mint (from is null).
func (v *TransferEventValidator) IsMint() *TransferEventValidator {
arr := v.event.Item.Value().([]stackitem.Item)
require.Equal(v.t, stackitem.AnyT, arr[0].Type(), "expected null sender for mint")
return v
}
// IsBurn checks that this is a burn (to is null).
func (v *TransferEventValidator) IsBurn() *TransferEventValidator {
arr := v.event.Item.Value().([]stackitem.Item)
require.Equal(v.t, stackitem.AnyT, arr[1].Type(), "expected null recipient for burn")
return v
}