package tutustest import ( "testing" "github.com/tutus-one/tutus-chain/pkg/core/state" "github.com/tutus-one/tutus-chain/pkg/util" "github.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 }