package modules import ( "context" "fmt" "strconv" "strings" "testing" "time" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/sonr-io/sonr/test/e2e/client" "github.com/sonr-io/sonr/test/e2e/utils" ) // EventEmissionTestSuite tests comprehensive event emissions across modules type EventEmissionTestSuite struct { suite.Suite cfg *utils.TestConfig ctx context.Context cancel context.CancelFunc // Test user addresses - using pre-funded localnet accounts userAddrs []string } func TestEventEmissionTestSuite(t *testing.T) { suite.Run(t, new(EventEmissionTestSuite)) } func (suite *EventEmissionTestSuite) SetupSuite() { suite.cfg = utils.NewTestConfig() suite.ctx, suite.cancel = context.WithTimeout(context.Background(), 10*time.Minute) // Use pre-funded accounts from localnet suite.userAddrs = []string{ "idx1fcqk3crpnyvyhtd4jepsnx5eat5ehc920epq29", // Pre-funded account 0 "idx10n78mn09nx0f056wam35wkfvanf37kepuj28x4", // Pre-funded account 1 "idx1xygwjmmj8rq3rq3k4adqvhd55x5yqjc8ktcm7e", // Pre-funded account 2 } } func (suite *EventEmissionTestSuite) TearDownSuite() { if suite.cancel != nil { suite.cancel() } } // TestDIDModuleEventEmissions tests all DID module events func (suite *EventEmissionTestSuite) TestDIDModuleEventEmissions() { suite.T().Log("Testing DID module event emissions") // Get current block height to filter events startHeight, err := suite.cfg.Client.GetLatestBlockHeight(suite.ctx) require.NoError(suite.T(), err, "failed to get start height") suite.T().Run("EventDIDCreated", func(t *testing.T) { suite.testDIDCreatedEvent(t, startHeight) }) suite.T().Run("EventVerificationMethodRemoved", func(t *testing.T) { suite.testVerificationMethodRemovedEvent(t, startHeight) }) suite.T().Run("EventServiceAdded", func(t *testing.T) { suite.testServiceAddedEvent(t, startHeight) }) suite.T().Run("EventServiceRemoved", func(t *testing.T) { suite.testServiceRemovedEvent(t, startHeight) }) suite.T().Run("EventWebAuthnRegistered", func(t *testing.T) { suite.testWebAuthnRegisteredEvent(t, startHeight) }) suite.T().Run("EventExternalWalletLinked", func(t *testing.T) { suite.testExternalWalletLinkedEvent(t, startHeight) }) } // TestDWNModuleEventEmissions tests all DWN module events func (suite *EventEmissionTestSuite) TestDWNModuleEventEmissions() { suite.T().Log("Testing DWN module event emissions") // Get current block height to filter events startHeight, err := suite.cfg.Client.GetLatestBlockHeight(suite.ctx) require.NoError(suite.T(), err, "failed to get start height") suite.T().Run("EventRecordWritten", func(t *testing.T) { suite.testRecordWrittenEvent(t, startHeight) }) suite.T().Run("EventProtocolConfigured", func(t *testing.T) { suite.testProtocolConfiguredEvent(t, startHeight) }) suite.T().Run("EventPermissionGranted", func(t *testing.T) { suite.testPermissionGrantedEvent(t, startHeight) }) suite.T().Run("EventPermissionRevoked", func(t *testing.T) { suite.testPermissionRevokedEvent(t, startHeight) }) suite.T().Run("EventVaultCreated", func(t *testing.T) { suite.testVaultCreatedEvent(t, startHeight) }) suite.T().Run("EventVaultKeysRotated", func(t *testing.T) { suite.testVaultKeysRotatedEvent(t, startHeight) }) } // TestEventPersistenceAndReplay tests that events persist and can be replayed func (suite *EventEmissionTestSuite) TestEventPersistenceAndReplay() { suite.T().Log("Testing event persistence and replay") // Record the current height currentHeight, err := suite.cfg.Client.GetLatestBlockHeight(suite.ctx) require.NoError(suite.T(), err, "failed to get current height") // Create a test DID to generate events testDID := fmt.Sprintf("did:sonr:persistence-test-%d", time.Now().Unix()) txResp := suite.createTestDID(suite.T(), testDID, suite.userAddrs[0]) // Wait for transaction to be included _, err = suite.cfg.Client.WaitForTx(suite.ctx, txResp.TxHash, 30*time.Second) require.NoError(suite.T(), err, "failed to wait for transaction") // Get the block height where the transaction was included txHeight := txResp.Height txHeightInt, err := strconv.ParseInt(txHeight, 10, 64) require.NoError(suite.T(), err, "failed to parse tx height") suite.T().Run("events_persist_across_queries", func(t *testing.T) { // Query events by height multiple times to ensure consistency for i := 0; i < 3; i++ { blockEvents, err := suite.cfg.Client.QueryEventsByHeight(suite.ctx, txHeightInt) require.NoError(t, err, "failed to query events by height on attempt %d", i+1) require.NotNil(t, blockEvents, "block events should not be nil") // Verify that the same events are returned each time found := false for _, txEvents := range blockEvents.TxEvents { if txEvents.TxHash == txResp.TxHash { found = true // Verify DID created event is present (simplified check) require.NotEmpty(t, txEvents.Events, "transaction should have events") break } } require.True(t, found, "transaction events should be found in block events") } }) suite.T().Run("events_queryable_by_attribute", func(t *testing.T) { // Query events by DID attribute events, err := suite.cfg.Client.QueryEventsByAttribute(suite.ctx, "did", testDID, currentHeight, 0) require.NoError(t, err, "failed to query events by attribute") // Should find at least the DID creation event foundDIDEvent := false for _, event := range events.Events { if event.Type == "did.v1.EventDIDCreated" { foundDIDEvent = true break } } require.True(t, foundDIDEvent, "should find DID created event by attribute query") }) } // TestEventQuerying tests various CometBFT query syntax patterns func (suite *EventEmissionTestSuite) TestEventQuerying() { suite.T().Log("Testing event querying with CometBFT syntax") startHeight, err := suite.cfg.Client.GetLatestBlockHeight(suite.ctx) require.NoError(suite.T(), err, "failed to get start height") // Create multiple test DIDs for complex querying testDIDs := []string{ fmt.Sprintf("did:sonr:query-test-1-%d", time.Now().Unix()), fmt.Sprintf("did:sonr:query-test-2-%d", time.Now().Unix()), fmt.Sprintf("did:sonr:query-test-3-%d", time.Now().Unix()), } var txHashes []string for i, testDID := range testDIDs { txResp := suite.createTestDID(suite.T(), testDID, suite.userAddrs[i%len(suite.userAddrs)]) txHashes = append(txHashes, txResp.TxHash) // Wait for transaction _, err = suite.cfg.Client.WaitForTx(suite.ctx, txResp.TxHash, 30*time.Second) require.NoError(suite.T(), err, "failed to wait for transaction %s", txResp.TxHash) } endHeight, err := suite.cfg.Client.GetLatestBlockHeight(suite.ctx) require.NoError(suite.T(), err, "failed to get end height") suite.T().Run("query_by_event_type", func(t *testing.T) { events, err := suite.cfg.Client.QueryEventsByType(suite.ctx, "did.v1.EventDIDCreated", startHeight, endHeight) require.NoError(t, err, "failed to query by event type") // Should find at least our test events foundCount := 0 for _, event := range events.Events { if event.Type == "did.v1.EventDIDCreated" { foundCount++ } } require.GreaterOrEqual(t, foundCount, len(testDIDs), "should find at least %d DID created events", len(testDIDs)) }) suite.T().Run("query_by_creator", func(t *testing.T) { // Query events by specific creator events, err := suite.cfg.Client.QueryEventsByAttribute(suite.ctx, "creator", suite.userAddrs[0], startHeight, endHeight) require.NoError(t, err, "failed to query by creator") // Should find events created by this user foundUserEvents := false for _, event := range events.Events { if event.Type == "did.v1.EventDIDCreated" { foundUserEvents = true break } } require.True(t, foundUserEvents, "should find events created by specific user") }) suite.T().Run("complex_query_patterns", func(t *testing.T) { // Test complex query with multiple conditions query := fmt.Sprintf("message.sender='%s' AND tx.height>=%d", suite.userAddrs[0], startHeight) events, err := suite.cfg.Client.SearchEvents(suite.ctx, query, startHeight, endHeight) require.NoError(t, err, "failed to execute complex query") // Should find some events require.NotEmpty(t, events.Events, "complex query should return some events") }) } // TestMultiEventTransactions tests transactions that emit multiple events func (suite *EventEmissionTestSuite) TestMultiEventTransactions() { suite.T().Log("Testing multi-event transactions") // Create a DID with multiple verification methods and services // This should emit multiple events in a single transaction testDID := fmt.Sprintf("did:sonr:multi-event-test-%d", time.Now().Unix()) // For this test, we'll simulate a transaction that creates a DID with services // which should emit both EventDIDCreated and EventServiceAdded txResp := suite.createTestDIDWithService(suite.T(), testDID, suite.userAddrs[0]) // Wait for transaction to be included finalTx, err := suite.cfg.Client.WaitForTx(suite.ctx, txResp.TxHash, 30*time.Second) require.NoError(suite.T(), err, "failed to wait for transaction") // Verify multiple events were emitted in the correct order events := finalTx.TxResponse.Events require.NotEmpty(suite.T(), events, "transaction should emit events") // Look for DID creation events didCreatedEvents := client.FilterEventsByType(events, "EventDIDCreated") require.NotEmpty(suite.T(), didCreatedEvents, "should emit EventDIDCreated") // Look for service addition events if services were added serviceAddedEvents := client.FilterEventsByType(events, "EventServiceAdded") // Note: This might be empty if the current implementation doesn't emit service events during DID creation _ = serviceAddedEvents // Avoid unused variable warning suite.T().Run("events_have_correct_order", func(t *testing.T) { // Events should be in a logical order // For DID creation, EventDIDCreated should come before any EventServiceAdded didCreatedIndex := -1 serviceAddedIndex := -1 for i, event := range events { if event.Type == "did.v1.EventDIDCreated" { didCreatedIndex = i } if event.Type == "did.v1.EventServiceAdded" { serviceAddedIndex = i } } require.NotEqual(t, -1, didCreatedIndex, "should find EventDIDCreated") if serviceAddedIndex != -1 { require.Less(t, didCreatedIndex, serviceAddedIndex, "EventDIDCreated should come before EventServiceAdded") } }) suite.T().Run("events_have_consistent_block_height", func(t *testing.T) { // All events in the same transaction should have the same block height expectedHeight := finalTx.TxResponse.Height for _, event := range events { // Check if this is one of our custom events if event.Type == "did.v1.EventDIDCreated" || event.Type == "did.v1.EventServiceAdded" { // Verify block height attribute if present if blockHeight, found := client.GetEventAttribute(event, "block_height"); found { require.Equal(t, expectedHeight, blockHeight, "event block height should match transaction height") } } } }) } // TestEventSubscription tests WebSocket event subscription func (suite *EventEmissionTestSuite) TestEventSubscription() { suite.T().Log("Testing event subscription via WebSocket") // Create WebSocket client wsClient := client.NewWebSocketClient("ws://localhost:26657") // CometBFT WebSocket endpoint err := wsClient.Connect(suite.ctx) if err != nil { suite.T().Skipf("WebSocket connection failed, skipping subscription tests: %v", err) return } defer wsClient.Close() suite.T().Run("subscribe_to_new_blocks", func(t *testing.T) { // Subscribe to new block events subscription, err := wsClient.SubscribeToNewBlockHeaders(suite.ctx) require.NoError(t, err, "failed to subscribe to new block headers") defer subscription.Close() // Wait for at least one block event event, err := subscription.WaitForEvent(suite.ctx, 30*time.Second, nil) require.NoError(t, err, "failed to receive block event") require.NotNil(t, event, "block event should not be nil") t.Logf("Received block event: %+v", event) }) suite.T().Run("subscribe_to_tx_events", func(t *testing.T) { // Subscribe to transaction events subscription, err := wsClient.SubscribeToTxEvents(suite.ctx) require.NoError(t, err, "failed to subscribe to transaction events") defer subscription.Close() // Create a transaction to trigger an event testDID := fmt.Sprintf("did:sonr:websocket-test-%d", time.Now().Unix()) txResp := suite.createTestDID(t, testDID, suite.userAddrs[0]) // Wait for the transaction event event, err := subscription.WaitForEvent(suite.ctx, 30*time.Second, func(event *client.SubscriptionEvent) bool { // Check if this event relates to our transaction eventStr := fmt.Sprintf("%v", event.Data.Value) return strings.Contains(eventStr, txResp.TxHash) }) if err != nil { t.Logf("Transaction event subscription test skipped (requires real transactions): %v", err) } else { require.NotNil(t, event, "transaction event should not be nil") t.Logf("Received transaction event: %+v", event) } }) suite.T().Run("subscribe_to_did_events", func(t *testing.T) { // Subscribe to DID-specific events subscription, err := wsClient.SubscribeToDIDEvents(suite.ctx) if err != nil { t.Skipf("DID event subscription failed (may require specific CometBFT configuration): %v", err) return } defer subscription.Close() // Create a DID to trigger an event testDID := fmt.Sprintf("did:sonr:did-sub-test-%d", time.Now().Unix()) _ = suite.createTestDID(t, testDID, suite.userAddrs[0]) // Wait for the DID event event, err := subscription.WaitForEventByType(suite.ctx, 30*time.Second, "EventDIDCreated") if err != nil { t.Logf("DID event subscription test skipped (requires real DID transactions): %v", err) } else { require.NotNil(t, event, "DID event should not be nil") t.Logf("Received DID event: %+v", event) } }) suite.T().Run("subscribe_with_custom_query", func(t *testing.T) { // Subscribe to events with a custom query customQuery := "tx.height > 1" subscription, err := wsClient.Subscribe(suite.ctx, customQuery) require.NoError(t, err, "failed to subscribe with custom query") defer subscription.Close() // Wait for any event matching the query event, err := subscription.WaitForEvent(suite.ctx, 30*time.Second, nil) if err != nil { t.Logf("Custom query subscription test result: %v", err) } else { require.NotNil(t, event, "custom query event should not be nil") t.Logf("Received custom query event: %+v", event) } }) } // TestEventAttributeValidation tests that event attributes are correctly populated func (suite *EventEmissionTestSuite) TestEventAttributeValidation() { suite.T().Log("Testing event attribute validation") testDID := fmt.Sprintf("did:sonr:attr-test-%d", time.Now().Unix()) creator := suite.userAddrs[0] // Create a test DID txResp := suite.createTestDID(suite.T(), testDID, creator) // Wait for transaction finalTx, err := suite.cfg.Client.WaitForTx(suite.ctx, txResp.TxHash, 30*time.Second) require.NoError(suite.T(), err, "failed to wait for transaction") // Find the DID created event events := finalTx.TxResponse.Events didCreatedEvents := client.FilterEventsByType(events, "EventDIDCreated") require.NotEmpty(suite.T(), didCreatedEvents, "should emit EventDIDCreated") didEvent := didCreatedEvents[0] suite.T().Run("required_attributes_present", func(t *testing.T) { // Check that required attributes are present requiredAttrs := []string{"did", "creator"} for _, requiredAttr := range requiredAttrs { value, found := client.GetEventAttribute(didEvent, requiredAttr) require.True(t, found, "attribute %s should be present", requiredAttr) require.NotEmpty(t, value, "attribute %s should not be empty", requiredAttr) } }) suite.T().Run("attribute_values_correct", func(t *testing.T) { // Verify specific attribute values if didValue, found := client.GetEventAttribute(didEvent, "did"); found { require.Contains(t, didValue, testDID, "DID attribute should contain test DID") } if creatorValue, found := client.GetEventAttribute(didEvent, "creator"); found { require.Contains(t, creatorValue, creator, "creator attribute should contain creator address") } // Check for block height if present if heightValue, found := client.GetEventAttribute(didEvent, "block_height"); found { require.NotEmpty(t, heightValue, "block height should not be empty") require.Equal(t, finalTx.TxResponse.Height, heightValue, "block height should match transaction height") } }) } // Helper methods for creating test transactions func (suite *EventEmissionTestSuite) createTestDID(t *testing.T, didID, creator string) *client.TxResponse { // This is a placeholder - in a real implementation, you would: // 1. Build a proper MsgCreateDID transaction // 2. Sign it with the creator's key // 3. Broadcast it to the network // For now, we'll simulate this by creating a mock transaction response // In the actual implementation, you would use the actual transaction building logic t.Logf("Creating test DID: %s by creator: %s", didID, creator) // Placeholder - replace with actual transaction building and broadcasting return &client.TxResponse{ TxHash: fmt.Sprintf("mock-tx-%d", time.Now().Unix()), Code: 0, Height: fmt.Sprintf("%d", time.Now().Unix()), Events: []struct { Type string `json:"type"` Attributes []struct { Key string `json:"key"` Value string `json:"value"` } `json:"attributes"` }{ { Type: "did.v1.EventDIDCreated", Attributes: []struct { Key string `json:"key"` Value string `json:"value"` }{ {Key: "did", Value: didID}, {Key: "creator", Value: creator}, {Key: "block_height", Value: fmt.Sprintf("%d", time.Now().Unix())}, }, }, }, } } func (suite *EventEmissionTestSuite) createTestDIDWithService(t *testing.T, didID, creator string) *client.TxResponse { // Similar to createTestDID but includes service creation t.Logf("Creating test DID with service: %s by creator: %s", didID, creator) return &client.TxResponse{ TxHash: fmt.Sprintf("mock-tx-with-service-%d", time.Now().Unix()), Code: 0, Height: fmt.Sprintf("%d", time.Now().Unix()), Events: []struct { Type string `json:"type"` Attributes []struct { Key string `json:"key"` Value string `json:"value"` } `json:"attributes"` }{ { Type: "did.v1.EventDIDCreated", Attributes: []struct { Key string `json:"key"` Value string `json:"value"` }{ {Key: "did", Value: didID}, {Key: "creator", Value: creator}, {Key: "block_height", Value: fmt.Sprintf("%d", time.Now().Unix())}, }, }, { Type: "did.v1.EventServiceAdded", Attributes: []struct { Key string `json:"key"` Value string `json:"value"` }{ {Key: "did", Value: didID}, {Key: "service_id", Value: didID + "#service-1"}, {Key: "type", Value: "LinkedDomains"}, {Key: "endpoint", Value: "https://example.com"}, }, }, }, } } // Individual event test methods func (suite *EventEmissionTestSuite) testDIDCreatedEvent(t *testing.T, startHeight int64) { t.Log("Testing EventDIDCreated emission") testDID := fmt.Sprintf("did:sonr:created-test-%d", time.Now().Unix()) txResp := suite.createTestDID(t, testDID, suite.userAddrs[0]) // Verify event was emitted didEvents := client.FilterEventsByType(txResp.Events, "EventDIDCreated") require.NotEmpty(t, didEvents, "should emit EventDIDCreated") // Verify event attributes didEvent := didEvents[0] didValue, found := client.GetEventAttribute(didEvent, "did") require.True(t, found, "DID attribute should be present") require.Contains(t, didValue, testDID, "DID value should match") } func (suite *EventEmissionTestSuite) testVerificationMethodRemovedEvent(t *testing.T, startHeight int64) { t.Log("Testing EventVerificationMethodRemoved emission") // Implementation would involve: // 1. Create a DID with verification methods // 2. Remove a verification method // 3. Verify EventVerificationMethodRemoved is emitted t.Skip("Implementation requires actual transaction building - placeholder for future implementation") } func (suite *EventEmissionTestSuite) testServiceAddedEvent(t *testing.T, startHeight int64) { t.Log("Testing EventServiceAdded emission") // Implementation would involve: // 1. Create or update a DID to add a service // 2. Verify EventServiceAdded is emitted with correct attributes t.Skip("Implementation requires actual transaction building - placeholder for future implementation") } func (suite *EventEmissionTestSuite) testServiceRemovedEvent(t *testing.T, startHeight int64) { t.Log("Testing EventServiceRemoved emission") t.Skip("Implementation requires actual transaction building - placeholder for future implementation") } func (suite *EventEmissionTestSuite) testWebAuthnRegisteredEvent(t *testing.T, startHeight int64) { t.Log("Testing EventWebAuthnRegistered emission") t.Skip("Implementation requires actual WebAuthn transaction building - placeholder for future implementation") } func (suite *EventEmissionTestSuite) testExternalWalletLinkedEvent(t *testing.T, startHeight int64) { t.Log("Testing EventExternalWalletLinked emission") t.Skip("Implementation requires actual wallet linking transaction - placeholder for future implementation") } func (suite *EventEmissionTestSuite) testRecordWrittenEvent(t *testing.T, startHeight int64) { t.Log("Testing EventRecordWritten emission") t.Skip("Implementation requires actual DWN record transaction building - placeholder for future implementation") } func (suite *EventEmissionTestSuite) testProtocolConfiguredEvent(t *testing.T, startHeight int64) { t.Log("Testing EventProtocolConfigured emission") t.Skip("Implementation requires actual protocol configuration transaction - placeholder for future implementation") } func (suite *EventEmissionTestSuite) testPermissionGrantedEvent(t *testing.T, startHeight int64) { t.Log("Testing EventPermissionGranted emission") t.Skip("Implementation requires actual permission granting transaction - placeholder for future implementation") } func (suite *EventEmissionTestSuite) testPermissionRevokedEvent(t *testing.T, startHeight int64) { t.Log("Testing EventPermissionRevoked emission") t.Skip("Implementation requires actual permission revocation transaction - placeholder for future implementation") } func (suite *EventEmissionTestSuite) testVaultCreatedEvent(t *testing.T, startHeight int64) { t.Log("Testing EventVaultCreated emission") t.Skip("Implementation requires actual vault creation transaction - placeholder for future implementation") } func (suite *EventEmissionTestSuite) testVaultKeysRotatedEvent(t *testing.T, startHeight int64) { t.Log("Testing EventVaultKeysRotated emission") t.Skip("Implementation requires actual key rotation transaction - placeholder for future implementation") }