296 lines
8.8 KiB
Go
Executable File
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
|
|
}
|