From cb7e421f5e4611281fc1a798ab9b0e5721f29d1d Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Mon, 8 Sep 2025 17:35:39 -0300 Subject: [PATCH 01/18] feat(tests): add ERC-20 bridge testing utilities and integration tests This commit introduces new test files and utilities for testing the ERC-20 bridge functionality. Key additions include: - `erc20_bridge_simple_test.go`: A minimal integration test to validate the initialization of the ERC-20 bridge and the balance retrieval functionality. - `simple_mock.sql`: A seed script for testing the ERC-20 bridge configuration. - New helper functions in `testutils` for simulating ERC-20 deposit and withdrawal events, enhancing the testing framework for the bridge. - Refactored existing test utilities to support the new ERC-20 bridge testing capabilities. These changes aim to improve the test coverage and reliability of the ERC-20 bridge functionality within the project. --- .../erc20/erc20_bridge_simple_test.go | 57 +++ tests/extensions/erc20/simple_mock.sql | 11 + .../aggr01_basic_aggregation_test.go | 2 +- .../aggr02_weighted_contributions_test.go | 2 +- .../aggr03_taxonomy_validity_periods_test.go | 2 +- .../aggr04_missing_data_handling_test.go | 4 +- .../aggr05_no_duplicate_child_streams_test.go | 2 +- .../aggr06_single_active_taxonomy_test.go | 2 +- .../aggr07_inexistent_streams_test.go | 2 +- .../aggregation/aggr08_weight_change_test.go | 2 +- .../aggr09_duplicate_values_test.go | 2 +- tests/streams/complex_composed_test.go | 21 +- tests/streams/utils/cache/helper.go | 172 +++++++ tests/streams/utils/cache/options.go | 138 +++++ tests/streams/utils/erc20/config.go | 167 ++++++ tests/streams/utils/erc20/helper.go | 161 ++++++ tests/streams/utils/erc20/mocks.go | 87 ++++ tests/streams/utils/erc20_bridge_test.go | 60 +++ tests/streams/utils/eventstore/mock.go | 100 ++++ tests/streams/utils/example_test.go | 79 +++ tests/streams/utils/runner.go | 222 ++++++++ tests/streams/utils/service/service.go | 43 ++ tests/streams/utils/utils.go | 480 +++--------------- 23 files changed, 1391 insertions(+), 427 deletions(-) create mode 100644 tests/extensions/erc20/erc20_bridge_simple_test.go create mode 100644 tests/extensions/erc20/simple_mock.sql create mode 100644 tests/streams/utils/cache/helper.go create mode 100644 tests/streams/utils/cache/options.go create mode 100644 tests/streams/utils/erc20/config.go create mode 100644 tests/streams/utils/erc20/helper.go create mode 100644 tests/streams/utils/erc20/mocks.go create mode 100644 tests/streams/utils/erc20_bridge_test.go create mode 100644 tests/streams/utils/eventstore/mock.go create mode 100644 tests/streams/utils/example_test.go create mode 100644 tests/streams/utils/runner.go create mode 100644 tests/streams/utils/service/service.go diff --git a/tests/extensions/erc20/erc20_bridge_simple_test.go b/tests/extensions/erc20/erc20_bridge_simple_test.go new file mode 100644 index 000000000..2ebba39a9 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_simple_test.go @@ -0,0 +1,57 @@ +package tests + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + testutils "github.com/trufnetwork/node/tests/streams/utils" +) + +// Minimal integration to validate that the ERC-20 bridge extension can be initialized +// via USE and that balance() can be called (expected 0 without any deposits). +// Seed script: tests/extensions/erc20/simple_mock.sql +// Reference: Kwil ERC-20 Bridge Usage (balance/bridge): https://docs.kwil.com/docs/erc20-bridge/usage#bridge +func TestERC20BridgeSimpleBalance(t *testing.T) { + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ + Name: "erc20_bridge_simple_balance", + SeedScripts: []string{"./tests/extensions/erc20/simple_mock.sql"}, + FunctionTests: []kwilTesting.TestFunc{ + func(ctx context.Context, platform *kwilTesting.Platform) error { + // Arbitrary wallet address to query (no prior deposits => expected 0) + wallet := "0x1111111111111111111111111111111111110001" + + txCtx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: 1}, + Signer: platform.Deployer, // signer identity; not material for a read-only balance + Caller: "0x0000000000000000000000000000000000000000", + TxID: platform.Txid(), + } + engCtx := &common.EngineContext{TxContext: txCtx} + + // Call our seeded action which proxies to sepolia_bridge.balance($wallet) + // Created in simple_mock.sql + var got string + r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{wallet}, func(row *common.Row) error { + if len(row.Values) != 1 { + return fmt.Errorf("expected 1 column, got %d", len(row.Values)) + } + got = fmt.Sprintf("%v", row.Values[0]) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + // Expect 0 for no prior deposits + require.Equal(t, "0", got, "expected zero balance for fresh wallet without deposit events") + return nil + }, + }, + }, &testutils.Options{Options: testutils.GetTestOptions()}) +} diff --git a/tests/extensions/erc20/simple_mock.sql b/tests/extensions/erc20/simple_mock.sql new file mode 100644 index 000000000..e6f314b1d --- /dev/null +++ b/tests/extensions/erc20/simple_mock.sql @@ -0,0 +1,11 @@ +-- we'll use this to test configuration of the erc20 bridge in tests. +-- if we successfully emit events and this listens, then we know the bridge is configured correctly. +USE erc20 { + chain: 'sepolia', + escrow: '0x1111111111111111111111111111111111111111', +} AS sepolia_bridge; + +CREATE ACTION get_balance($wallet_address TEXT) public { + $balance := sepolia_bridge.balance($wallet_address); + return $balance; +} \ No newline at end of file diff --git a/tests/streams/aggregation/aggr01_basic_aggregation_test.go b/tests/streams/aggregation/aggr01_basic_aggregation_test.go index 900d099e0..8db5b8498 100644 --- a/tests/streams/aggregation/aggr01_basic_aggregation_test.go +++ b/tests/streams/aggregation/aggr01_basic_aggregation_test.go @@ -30,7 +30,7 @@ import ( // TestAGGR01_BasicAggregation tests AGGR01: A composed stream aggregates data from multiple child streams (which may be either primitive or composed). func TestAGGR01_BasicAggregation(t *testing.T) { // Cache all streams from this deployer - cacheConfig := testutils.TestCache("0x0000000000000000000000000000000000000123", "*") + cacheConfig := testutils.SimpleCache("0x0000000000000000000000000000000000000123", "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "aggr01_basic_aggregation_test", diff --git a/tests/streams/aggregation/aggr02_weighted_contributions_test.go b/tests/streams/aggregation/aggr02_weighted_contributions_test.go index 6013261a9..c84e20c8a 100644 --- a/tests/streams/aggregation/aggr02_weighted_contributions_test.go +++ b/tests/streams/aggregation/aggr02_weighted_contributions_test.go @@ -30,7 +30,7 @@ import ( // TestAGGR02_WeightedContributions tests AGGR02: Each child stream's contribution is weighted, and these weights can vary over time. func TestAGGR02_WeightedContributions(t *testing.T) { // Cache all streams from this deployer - cacheConfig := testutils.TestCache("0x0000000000000000000000000000000000000123", "*") + cacheConfig := testutils.SimpleCache("0x0000000000000000000000000000000000000123", "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "aggr02_weighted_contributions_test", diff --git a/tests/streams/aggregation/aggr03_taxonomy_validity_periods_test.go b/tests/streams/aggregation/aggr03_taxonomy_validity_periods_test.go index a3dd36bd4..6502ad60d 100644 --- a/tests/streams/aggregation/aggr03_taxonomy_validity_periods_test.go +++ b/tests/streams/aggregation/aggr03_taxonomy_validity_periods_test.go @@ -33,7 +33,7 @@ import ( // TestAGGR03_TaxonomyValidityPeriods tests AGGR03: Taxonomies define the mapping of child streams, including a period of validity for each weight. (start_date and end_date, otherwise not set) func TestAGGR03_TaxonomyValidityPeriods(t *testing.T) { - cacheConfig := testutils.TestCache("0x0000000000000000000000000000000000000123", "*") + cacheConfig := testutils.SimpleCache("0x0000000000000000000000000000000000000123", "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "aggr03_taxonomy_validity_periods_test", diff --git a/tests/streams/aggregation/aggr04_missing_data_handling_test.go b/tests/streams/aggregation/aggr04_missing_data_handling_test.go index 454106fa9..f15de9300 100644 --- a/tests/streams/aggregation/aggr04_missing_data_handling_test.go +++ b/tests/streams/aggregation/aggr04_missing_data_handling_test.go @@ -32,7 +32,7 @@ import ( // TestAGGR04_MissingDataHandling tests AGGR04: If a child stream doesn't have data for the given date (including last available data), the composed stream will not count it's weight for that date. func TestAGGR04_MissingDataHandling(t *testing.T) { - cacheConfig := testutils.TestCache("0x0000000000000000000000000000000000000123", "*") + cacheConfig := testutils.SimpleCache("0x0000000000000000000000000000000000000123", "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "aggr04_missing_data_handling_test", @@ -56,7 +56,7 @@ func testAGGR04_MissingDataHandling(t *testing.T, useCache bool) func(ctx contex if err != nil { return errors.Wrap(err, "error registering data provider") } - + // Setup the composed stream with 2 primitive streams // One stream has data for all 4 days // The other starts only on the 2nd day, and on 3rd day is missing too (has data on 2nd and 4th day) diff --git a/tests/streams/aggregation/aggr05_no_duplicate_child_streams_test.go b/tests/streams/aggregation/aggr05_no_duplicate_child_streams_test.go index ab65f038c..28167e604 100644 --- a/tests/streams/aggregation/aggr05_no_duplicate_child_streams_test.go +++ b/tests/streams/aggregation/aggr05_no_duplicate_child_streams_test.go @@ -26,7 +26,7 @@ import ( // TestAGGR05_NoDuplicateChildStreams tests AGGR05: For a single taxonomy version, there can't be duplicated child stream definitions. func TestAGGR05_NoDuplicateChildStreams(t *testing.T) { - cacheConfig := testutils.TestCache("0x0000000000000000000000000000000000000123", "*") + cacheConfig := testutils.SimpleCache("0x0000000000000000000000000000000000000123", "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "aggr05_no_duplicate_child_streams_test", diff --git a/tests/streams/aggregation/aggr06_single_active_taxonomy_test.go b/tests/streams/aggregation/aggr06_single_active_taxonomy_test.go index 3951482bc..f35a9d5dd 100644 --- a/tests/streams/aggregation/aggr06_single_active_taxonomy_test.go +++ b/tests/streams/aggregation/aggr06_single_active_taxonomy_test.go @@ -29,7 +29,7 @@ import ( // TestAGGR06_SingleActiveTaxonomy tests AGGR06: Only 1 taxonomy version can be active in a point in time. func TestAGGR06_SingleActiveTaxonomy(t *testing.T) { - cacheConfig := testutils.TestCache("0x0000000000000000000000000000000000000123", "*") + cacheConfig := testutils.SimpleCache("0x0000000000000000000000000000000000000123", "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "aggr06_single_active_taxonomy_test", diff --git a/tests/streams/aggregation/aggr07_inexistent_streams_test.go b/tests/streams/aggregation/aggr07_inexistent_streams_test.go index 692a137ee..eef8d0710 100644 --- a/tests/streams/aggregation/aggr07_inexistent_streams_test.go +++ b/tests/streams/aggregation/aggr07_inexistent_streams_test.go @@ -25,7 +25,7 @@ import ( // TestAGGR07_InexistentStreamsRejected tests that setting taxonomies with non-existent stream references results in errors func TestAGGR07_InexistentStreamsRejected(t *testing.T) { - cacheConfig := testutils.TestCache("0x0000000000000000000000000000000000000123", "*") + cacheConfig := testutils.SimpleCache("0x0000000000000000000000000000000000000123", "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "aggr07_inexistent_streams_test", diff --git a/tests/streams/aggregation/aggr08_weight_change_test.go b/tests/streams/aggregation/aggr08_weight_change_test.go index 66a97f327..36d73a64a 100644 --- a/tests/streams/aggregation/aggr08_weight_change_test.go +++ b/tests/streams/aggregation/aggr08_weight_change_test.go @@ -37,7 +37,7 @@ import ( // TestAGGR08_WeightChangeEventPoints tests that weight changes create event points in the composed stream func TestAGGR08_WeightChangeEventPoints(t *testing.T) { - cacheConfig := testutils.TestCache("0x0000000000000000000000000000000000000123", "*") + cacheConfig := testutils.SimpleCache("0x0000000000000000000000000000000000000123", "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "aggr08_weight_change_event_points_test", diff --git a/tests/streams/aggregation/aggr09_duplicate_values_test.go b/tests/streams/aggregation/aggr09_duplicate_values_test.go index 00c0d7560..a0711621a 100644 --- a/tests/streams/aggregation/aggr09_duplicate_values_test.go +++ b/tests/streams/aggregation/aggr09_duplicate_values_test.go @@ -39,7 +39,7 @@ import ( // TestAGGR09_DuplicateValues tests AGGR09: Duplicate values from multiple child streams are both counted in aggregation. func TestAGGR09_DuplicateValues(t *testing.T) { - cacheConfig := testutils.TestCache("0x0000000000000000000000000000000000000123", "*") + cacheConfig := testutils.SimpleCache("0x0000000000000000000000000000000000000123", "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "aggr09_duplicate_values_test", diff --git a/tests/streams/complex_composed_test.go b/tests/streams/complex_composed_test.go index ceb73af26..8831e0c9f 100644 --- a/tests/streams/complex_composed_test.go +++ b/tests/streams/complex_composed_test.go @@ -7,10 +7,13 @@ import ( "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" + "github.com/trufnetwork/node/tests/streams/utils/cache" "github.com/trufnetwork/node/tests/streams/utils/procedure" "github.com/trufnetwork/node/tests/streams/utils/setup" "github.com/trufnetwork/node/tests/streams/utils/table" + "github.com/trufnetwork/node/extensions/tn_cache" + "github.com/pkg/errors" "github.com/trufnetwork/sdk-go/core/types" "github.com/trufnetwork/sdk-go/core/util" @@ -26,7 +29,7 @@ var ( func TestComplexComposed(t *testing.T) { // Run with cache setup (will test both with and without cache) - cacheConfig := testutils.TestCache(complexComposedDeployer.Address(), "*") + cacheConfig := testutils.SimpleCache(complexComposedDeployer.Address(), "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "complex_composed_test", SeedScripts: migrations.GetSeedScriptPaths(), @@ -49,16 +52,16 @@ func TestComplexComposed(t *testing.T) { func wrapTestWithCacheModes(t *testing.T, testName string, testFunc func(*testing.T, bool) func(ctx context.Context, platform *kwilTesting.Platform, helper *testutils.CacheTestHelper) error) func(ctx context.Context, platform *kwilTesting.Platform, helper *testutils.CacheTestHelper) error { return func(ctx context.Context, platform *kwilTesting.Platform, helper *testutils.CacheTestHelper) error { t.Run(testName+"_without_cache", func(t *testing.T) { - helper.RunInTx(t, func(t *testing.T, txPlatform *kwilTesting.Platform) { - err := testFunc(t, false)(ctx, txPlatform, helper) + helper.RunInTx(t, func(t cache.TestingT, txPlatform *kwilTesting.Platform) { + err := testFunc(t.(*testing.T), false)(ctx, txPlatform, helper) if err != nil { t.Fatalf("Test failed without cache: %v", err) } }) }) t.Run(testName+"_with_cache", func(t *testing.T) { - helper.RunInTx(t, func(t *testing.T, txPlatform *kwilTesting.Platform) { - err := testFunc(t, true)(ctx, txPlatform, helper) + helper.RunInTx(t, func(t cache.TestingT, txPlatform *kwilTesting.Platform) { + err := testFunc(t.(*testing.T), true)(ctx, txPlatform, helper) if err != nil { t.Fatalf("Test failed with cache: %v", err) } @@ -111,12 +114,12 @@ func WithTestSetup(testFn func(ctx context.Context, platform *kwilTesting.Platfo } // Always set up cache (it will be used based on the UseCache parameter in queries) - cacheConfig := testutils.TestCache(complexComposedDeployer.Address(), "*") - helper := testutils.SetupCacheTest(ctx, platform, cacheConfig) + cacheConfig := testutils.SimpleCache(complexComposedDeployer.Address(), "*") + helper := cache.SetupCacheTest(ctx, platform, cacheConfig) defer helper.Cleanup() // Refresh cache - recordsCached, err := helper.RefreshAllStreamsSync(ctx) + recordsCached, err := tn_cache.GetTestHelper().RefreshAllStreamsSync(ctx) if err != nil { return errors.Wrap(err, "error refreshing cache") } @@ -695,7 +698,7 @@ func testComposedRecordNoDuplicates(t *testing.T, useCache bool) func(ctx contex // Refresh cache for the newly created stream if cache is being used if useCache { - _, err := helper.RefreshAllStreamsSync(ctx) + _, err := tn_cache.GetTestHelper().RefreshAllStreamsSync(ctx) if err != nil { return errors.Wrap(err, "error refreshing cache") } diff --git a/tests/streams/utils/cache/helper.go b/tests/streams/utils/cache/helper.go new file mode 100644 index 000000000..ee307b5f2 --- /dev/null +++ b/tests/streams/utils/cache/helper.go @@ -0,0 +1,172 @@ +// Package cache provides helper functions for cache extension testing +package cache + +import ( + "context" + "fmt" + + "github.com/trufnetwork/kwil-db/common" + "github.com/trufnetwork/kwil-db/config" + "github.com/trufnetwork/kwil-db/core/log" + "github.com/trufnetwork/kwil-db/node/types/sql" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + + // Import for extension registration + "github.com/trufnetwork/node/extensions/tn_cache" +) + +// CacheTestHelper provides utilities for cache-enabled tests +type CacheTestHelper struct { + cacheConfig *CacheOptions + platform *kwilTesting.Platform + testHelper *tn_cache.TestHelper +} + +// SetupCacheTest sets up cache configuration and test DB injection +func SetupCacheTest(ctx context.Context, platform *kwilTesting.Platform, cacheConfig *CacheOptions) *CacheTestHelper { + // Set up test database configuration (uses test container defaults) + tn_cache.SetTestDBConfiguration(config.DBConfig{ + Host: "localhost", + Port: "52853", // Default test container port + User: "kwild", + Pass: "kwild", + DBName: "kwil_test_db", + }) + + // Inject the test's database connection + tn_cache.SetTestDB(platform.DB) + + // Configure the extension + configMap := make(map[string]string) + for k, v := range cacheConfig.Build() { + configMap[k] = fmt.Sprintf("%v", v) + } + tn_cache.SetTestConfiguration(configMap) + + helper := &CacheTestHelper{ + cacheConfig: cacheConfig, + platform: platform, + } + helper.testHelper = tn_cache.GetTestHelper() + return helper +} + +// Cleanup cleans up test injection +func (h *CacheTestHelper) Cleanup() { + tn_cache.SetTestDB(nil) +} + +// IsCacheEnabled returns whether the cache is enabled +func (h *CacheTestHelper) IsCacheEnabled() bool { + return h.cacheConfig.enabled +} + +// GetDB returns the test database for direct queries +func (h *CacheTestHelper) GetDB() sql.DB { + return h.platform.DB +} + +// SetDB sets the test database for direct queries +func (h *CacheTestHelper) SetDB(db sql.DB) { + h.platform.DB = db + tn_cache.SetTestDB(db) +} + +// WithCache is a test wrapper that sets up and tears down cache configuration +func WithCache(t TestingT, ctx context.Context, platform *kwilTesting.Platform, cacheConfig *CacheOptions, testFunc func(*CacheTestHelper)) { + helper := SetupCacheTest(ctx, platform, cacheConfig) + defer helper.Cleanup() + testFunc(helper) +} + +// RunInTx runs the provided test function within a database transaction +func (h *CacheTestHelper) RunInTx(t TestingT, testFn func(t TestingT, txPlatform *kwilTesting.Platform)) { + WithTx(h.platform, func(t TestingT, txPlatform *kwilTesting.Platform) { + // Save original DB and set tx DB + originalDB := h.GetDB() + h.SetDB(txPlatform.DB) + defer h.SetDB(originalDB) // Restore after + + testFn(t, txPlatform) + })(t) +} + +// WithTx runs testFunc inside a real DB transaction and rolls back at the end +func WithTx(platform *kwilTesting.Platform, testFunc func(TestingT, *kwilTesting.Platform)) func(TestingT) { + return func(t TestingT) { + tx, err := platform.DB.BeginTx(context.Background()) + if err != nil { + t.Fatalf("begin tx: %v", err) + return + } + defer tx.Rollback(context.Background()) + + txPlatform := &kwilTesting.Platform{ + Engine: platform.Engine, + DB: tx, + Deployer: platform.Deployer, + Logger: platform.Logger, + } + + testFunc(t, txPlatform) + } +} + +// TestingT interface for test functions +type TestingT interface { + Fatalf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) +} + +// SetupCacheExtension creates a full cache extension setup for testing +func SetupCacheExtension(ctx context.Context, cacheConfig *CacheOptions, platform *kwilTesting.Platform) error { + // Setup basic cache test + helper := SetupCacheTest(ctx, platform, cacheConfig) + defer helper.Cleanup() + + // Create mock service for extension setup + mockService := &common.Service{ + Logger: log.NewStdoutLogger(), + LocalConfig: &config.Config{ + DB: config.DBConfig{ + Host: "localhost", + Port: "52853", + User: "kwild", + Pass: "kwild", + DBName: "kwil_test_db", + }, + Extensions: map[string]map[string]string{ + tn_cache.ExtensionName: cacheConfig.Build(), + }, + }, + } + + // Parse and setup extension + processedConfig, err := tn_cache.ParseConfig(mockService) + if err != nil { + return fmt.Errorf("failed to parse config: %w", err) + } + + ext, err := tn_cache.SetupCacheExtension(ctx, processedConfig, platform.Engine, mockService) + if err != nil { + return fmt.Errorf("failed to setup extension: %w", err) + } + + // Register extension for cleanup + tn_cache.SetExtension(ext) + defer func() { + tn_cache.SetExtension(nil) + // Stop background tasks + if ext.Scheduler() != nil { + if stopErr := ext.Scheduler().Stop(); stopErr != nil { + mockService.Logger.Error("failed to stop scheduler", "error", stopErr) + } + } + if ext.SyncChecker() != nil { + ext.SyncChecker().Stop() + } + ext.Close() + }() + + return nil +} diff --git a/tests/streams/utils/cache/options.go b/tests/streams/utils/cache/options.go new file mode 100644 index 000000000..4315867fb --- /dev/null +++ b/tests/streams/utils/cache/options.go @@ -0,0 +1,138 @@ +// Package cache provides configuration options for cache extension testing +package cache + +import ( + "encoding/json" + "fmt" + "time" +) + +// CacheOptions provides a fluent API for configuring the tn_cache extension +type CacheOptions struct { + enabled bool + resolutionSchedule string + maxBlockAge string + streams []StreamConfig +} + +// StreamConfig represents a single stream to cache +type StreamConfig struct { + DataProvider string `json:"data_provider"` + StreamID string `json:"stream_id"` + CronSchedule string `json:"cron_schedule"` + From *int64 `json:"from,omitempty"` + IncludeChildren bool `json:"include_children,omitempty"` +} + +// NewCacheOptions creates a new cache configuration with defaults +func NewCacheOptions() *CacheOptions { + return &CacheOptions{ + enabled: false, // Default to disabled + } +} + +// WithEnabled enables the cache extension +func (c *CacheOptions) WithEnabled() *CacheOptions { + c.enabled = true + return c +} + +// WithDisabled explicitly disables the cache extension (default) +func (c *CacheOptions) WithDisabled() *CacheOptions { + c.enabled = false + return c +} + +// WithResolutionSchedule sets the schedule for re-resolving wildcards (default: daily at midnight) +func (c *CacheOptions) WithResolutionSchedule(schedule string) *CacheOptions { + c.resolutionSchedule = schedule + return c +} + +// WithMaxBlockAge sets the maximum age of blocks to consider the node synced +func (c *CacheOptions) WithMaxBlockAge(duration time.Duration) *CacheOptions { + c.maxBlockAge = duration.String() + return c +} + +// WithStream adds a stream to cache +func (c *CacheOptions) WithStream(dataProvider, streamID, cronSchedule string) *CacheOptions { + c.streams = append(c.streams, StreamConfig{ + DataProvider: dataProvider, + StreamID: streamID, + CronSchedule: cronSchedule, + }) + return c +} + +// WithStreamFromTime adds a stream with a specific start time +func (c *CacheOptions) WithStreamFromTime(dataProvider, streamID, cronSchedule string, fromTime int64) *CacheOptions { + c.streams = append(c.streams, StreamConfig{ + DataProvider: dataProvider, + StreamID: streamID, + CronSchedule: cronSchedule, + From: &fromTime, + }) + return c +} + +// WithComposedStream adds a composed stream with children included +func (c *CacheOptions) WithComposedStream(dataProvider, streamID, cronSchedule string, includeChildren bool) *CacheOptions { + c.streams = append(c.streams, StreamConfig{ + DataProvider: dataProvider, + StreamID: streamID, + CronSchedule: cronSchedule, + IncludeChildren: includeChildren, + }) + return c +} + +// WithWildcardProvider adds all streams from a provider +func (c *CacheOptions) WithWildcardProvider(dataProvider, cronSchedule string) *CacheOptions { + c.streams = append(c.streams, StreamConfig{ + DataProvider: dataProvider, + StreamID: "*", + CronSchedule: cronSchedule, + }) + return c +} + +// Build converts the options into the metadata map for the extension +func (c *CacheOptions) Build() map[string]string { + metadata := make(map[string]string) + + // Always set enabled status + metadata["enabled"] = fmt.Sprintf("%v", c.enabled) + + // Only add other options if cache is enabled + if c.enabled { + if c.resolutionSchedule != "" { + metadata["resolution_schedule"] = c.resolutionSchedule + } + + if c.maxBlockAge != "" { + metadata["max_block_age"] = c.maxBlockAge + } + + if len(c.streams) > 0 { + // Convert streams to JSON + streamsJSON, err := json.Marshal(c.streams) + if err != nil { + panic(fmt.Sprintf("failed to marshal cache streams: %v", err)) + } + metadata["streams_inline"] = string(streamsJSON) + } + } + + return metadata +} + +// IsEnabled returns whether the cache is enabled +func (c *CacheOptions) IsEnabled() bool { + return c.enabled +} + +// GetStreams returns the configured streams +func (c *CacheOptions) GetStreams() []StreamConfig { + return c.streams +} diff --git a/tests/streams/utils/erc20/config.go b/tests/streams/utils/erc20/config.go new file mode 100644 index 000000000..45b2cb2b8 --- /dev/null +++ b/tests/streams/utils/erc20/config.go @@ -0,0 +1,167 @@ +// Package erc20 provides configuration and setup for ERC-20 bridge testing +package erc20 + +import ( + "fmt" + "net/url" + "os" + "strings" + "time" + + "github.com/trufnetwork/kwil-db/config" + "github.com/trufnetwork/kwil-db/extensions/listeners" +) + +// ERC20BridgeConfig provides configuration for ERC-20 bridge testing +type ERC20BridgeConfig struct { + RPC map[string]string + Signers map[string]string + Chains map[string]*ERC20ChainConfig + Timeout time.Duration + AutoStart bool + MockListeners map[string]listeners.ListenFunc +} + +// ERC20ChainConfig represents per-chain configuration for ERC-20 bridge testing +type ERC20ChainConfig struct { + BlockSyncChunkSize string + MaxRetries int64 + ReconnectionInterval int64 +} + +// NewERC20BridgeConfig creates a new ERC-20 bridge configuration +func NewERC20BridgeConfig() *ERC20BridgeConfig { + return &ERC20BridgeConfig{ + RPC: make(map[string]string), + Signers: make(map[string]string), + Chains: make(map[string]*ERC20ChainConfig), + Timeout: 30 * time.Second, + AutoStart: false, + MockListeners: make(map[string]listeners.ListenFunc), + } +} + +// WithRPC adds an RPC endpoint for a chain +func (c *ERC20BridgeConfig) WithRPC(chain, rpcURL string) *ERC20BridgeConfig { + c.RPC[chain] = rpcURL + return c +} + +// WithSigner adds a signer configuration +func (c *ERC20BridgeConfig) WithSigner(name, keyPath string) *ERC20BridgeConfig { + c.Signers[name] = keyPath + return c +} + +// WithChainConfig adds chain-specific configuration +func (c *ERC20BridgeConfig) WithChainConfig(chain string, config *ERC20ChainConfig) *ERC20BridgeConfig { + c.Chains[chain] = config + return c +} + +// WithTimeout sets the listener timeout +func (c *ERC20BridgeConfig) WithTimeout(timeout time.Duration) *ERC20BridgeConfig { + c.Timeout = timeout + return c +} + +// WithAutoStart enables automatic listener startup +func (c *ERC20BridgeConfig) WithAutoStart() *ERC20BridgeConfig { + c.AutoStart = true + return c +} + +// WithMockListener adds a mock listener for testing +func (c *ERC20BridgeConfig) WithMockListener(name string, listener listeners.ListenFunc) *ERC20BridgeConfig { + c.MockListeners[name] = listener + return c +} + +// BuildERC20BridgeConfig converts the test config to kwil-db config format +func (c *ERC20BridgeConfig) BuildERC20BridgeConfig() *config.ERC20BridgeConfig { + erc20Config := &config.ERC20BridgeConfig{ + RPC: c.RPC, + BlockSyncChuckSize: make(map[string]string), + Signer: c.Signers, + } + + // Add chain-specific configurations + for chain, chainConfig := range c.Chains { + if chainConfig.BlockSyncChunkSize != "" { + erc20Config.BlockSyncChuckSize[chain] = chainConfig.BlockSyncChunkSize + } + } + + return erc20Config +} + +// Validate performs basic validation on the configuration +func (c *ERC20BridgeConfig) Validate() error { + // Validate RPC URLs + for chain, rpcURL := range c.RPC { + if rpcURL == "" { + return fmt.Errorf("RPC URL for chain %s cannot be empty", chain) + } + + // Parse URL to validate format + u, err := url.Parse(rpcURL) + if err != nil { + return fmt.Errorf("invalid RPC URL for chain %s: %w", chain, err) + } + + // Must be WebSocket protocol + if u.Scheme != "ws" && u.Scheme != "wss" { + return fmt.Errorf("RPC URL for chain %s must use WebSocket protocol (ws:// or wss://), got %s", chain, u.Scheme) + } + + // Must have a host + if u.Host == "" { + return fmt.Errorf("RPC URL for chain %s must specify a host", chain) + } + } + + // Validate signer configurations + for name, keyPath := range c.Signers { + if keyPath == "" { + return fmt.Errorf("signer key path for %s cannot be empty", name) + } + + // Skip validation for mock paths + if keyPath == "/dev/null" { + continue + } + + // Check if file exists + if _, err := os.Stat(keyPath); os.IsNotExist(err) { + return fmt.Errorf("signer key file for %s does not exist: %s", name, keyPath) + } + } + + // Validate timeout + if c.Timeout <= 0 { + return fmt.Errorf("timeout must be positive, got %v", c.Timeout) + } + + // Validate chain configurations + for chain, chainConfig := range c.Chains { + if chainConfig.BlockSyncChunkSize != "" { + // Validate chunk size format (should be a number) + if !strings.Contains(chainConfig.BlockSyncChunkSize, ".") { + // If it's an integer, try to parse it + if _, err := url.Parse(chainConfig.BlockSyncChunkSize); err == nil { + continue // It's a valid URL-like string + } + } + } + + if chainConfig.MaxRetries < 0 { + return fmt.Errorf("max retries for chain %s cannot be negative, got %d", chain, chainConfig.MaxRetries) + } + + if chainConfig.ReconnectionInterval < 0 { + return fmt.Errorf("reconnection interval for chain %s cannot be negative, got %d", chain, chainConfig.ReconnectionInterval) + } + } + + return nil +} diff --git a/tests/streams/utils/erc20/helper.go b/tests/streams/utils/erc20/helper.go new file mode 100644 index 000000000..56f45b0af --- /dev/null +++ b/tests/streams/utils/erc20/helper.go @@ -0,0 +1,161 @@ +// Package erc20 provides helper functions for ERC-20 bridge testing +package erc20 + +import ( + "context" + "fmt" + "time" + + "github.com/trufnetwork/kwil-db/common" + "github.com/trufnetwork/kwil-db/extensions/listeners" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/node/tests/streams/utils/eventstore" + "github.com/trufnetwork/node/tests/streams/utils/service" +) + +// ERC20BridgeTestHelper provides utilities for ERC-20 bridge testing +type ERC20BridgeTestHelper struct { + config *ERC20BridgeConfig + platform *kwilTesting.Platform + eventStore listeners.EventStore + service *common.Service + listenerCtx context.Context + cancel context.CancelFunc + listenerDone chan error + running bool +} + +// SetupERC20BridgeTest sets up ERC-20 bridge testing environment +func SetupERC20BridgeTest(ctx context.Context, platform *kwilTesting.Platform, config *ERC20BridgeConfig) (*ERC20BridgeTestHelper, error) { + helper := &ERC20BridgeTestHelper{ + config: config, + platform: platform, + listenerDone: make(chan error, 1), + } + + // Create event store + helper.eventStore = eventstore.NewMockEventStore() + + // Create service with ERC-20 bridge configuration + bridgeConfig := config.BuildERC20BridgeConfig() + svc, err := service.CreateBridgeService(bridgeConfig) + if err != nil { + return nil, fmt.Errorf("failed to create service: %w", err) + } + helper.service = svc + + return helper, nil +} + +// StartERC20Listener manually starts the ERC-20 bridge listener +func (h *ERC20BridgeTestHelper) StartERC20Listener(ctx context.Context) error { + if h.running { + return fmt.Errorf("listener already running") + } + + // Get the registered listener + listenerFunc, exists := listeners.GetListener("evm_sync") + if !exists { + return fmt.Errorf("evm_sync listener not registered - ensure kwil-db node/exts/evm-sync is imported") + } + + // Use mock listener if provided + if mockFunc, hasMock := h.config.MockListeners["evm_sync"]; hasMock { + listenerFunc = mockFunc + } + + // Create context with timeout + h.listenerCtx, h.cancel = context.WithTimeout(ctx, h.config.Timeout) + + // Start listener in background + h.running = true + go func() { + defer func() { h.running = false }() + err := listenerFunc(h.listenerCtx, h.service, h.eventStore) + h.listenerDone <- err + }() + + return nil +} + +// StopERC20Listener stops the running listener +func (h *ERC20BridgeTestHelper) StopERC20Listener() error { + if !h.running { + return nil + } + + if h.cancel != nil { + h.cancel() + } + + // Wait for listener to stop + select { + case err := <-h.listenerDone: + h.running = false + return err + case <-time.After(5 * time.Second): + return fmt.Errorf("listener did not stop within timeout") + } +} + +// Cleanup cleans up test resources +func (h *ERC20BridgeTestHelper) Cleanup() error { + return h.StopERC20Listener() +} + +// IsListenerRunning returns whether the listener is currently running +func (h *ERC20BridgeTestHelper) IsListenerRunning() bool { + return h.running +} + +// GetService returns the service instance for advanced testing +func (h *ERC20BridgeTestHelper) GetService() *common.Service { + return h.service +} + +// GetEventStore returns the event store for advanced testing +func (h *ERC20BridgeTestHelper) GetEventStore() listeners.EventStore { + return h.eventStore +} + +// WaitForListenerEvent waits for a specific event to be broadcast +func (h *ERC20BridgeTestHelper) WaitForListenerEvent(eventType string, timeout time.Duration) error { + if mockStore, ok := h.eventStore.(*eventstore.MockEventStore); ok { + return mockStore.WaitForEvent(eventType, timeout) + } + return fmt.Errorf("event store does not support event waiting") +} + +// WithERC20Bridge is a test wrapper that sets up and tears down ERC-20 bridge testing +func WithERC20Bridge(t TestingT, ctx context.Context, platform *kwilTesting.Platform, config *ERC20BridgeConfig, testFunc func(*ERC20BridgeTestHelper)) { + helper, err := SetupERC20BridgeTest(ctx, platform, config) + if err != nil { + t.Fatalf("Failed to setup ERC-20 bridge: %v", err) + } + defer func() { + if err := helper.Cleanup(); err != nil { + t.Errorf("Failed to cleanup ERC-20 bridge: %v", err) + } + }() + + // Auto-start listener if configured + if config.AutoStart { + if err := helper.StartERC20Listener(ctx); err != nil { + t.Fatalf("Failed to start ERC-20 listener: %v", err) + } + } + + testFunc(helper) +} + +// WithERC20BridgeAndListener combines setup and auto-start +func WithERC20BridgeAndListener(t TestingT, ctx context.Context, platform *kwilTesting.Platform, config *ERC20BridgeConfig, testFunc func(*ERC20BridgeTestHelper)) { + config.AutoStart = true + WithERC20Bridge(t, ctx, platform, config, testFunc) +} + +// TestingT interface for test functions +type TestingT interface { + Fatalf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) +} diff --git a/tests/streams/utils/erc20/mocks.go b/tests/streams/utils/erc20/mocks.go new file mode 100644 index 000000000..5ce9998bc --- /dev/null +++ b/tests/streams/utils/erc20/mocks.go @@ -0,0 +1,87 @@ +// Package erc20 provides mock implementations for ERC-20 bridge testing +package erc20 + +import ( + "context" + "fmt" + + "github.com/trufnetwork/kwil-db/common" + "github.com/trufnetwork/kwil-db/extensions/listeners" +) + +// CreateMockERC20Transfer creates a mock ERC-20 transfer event +func CreateMockERC20Transfer(from, to, amount string) []byte { + // This would create a properly formatted Ethereum log event + // For now, return a simple JSON representation + return []byte(fmt.Sprintf(`{"from":"%s","to":"%s","amount":"%s"}`, from, to, amount)) +} + +// CreateMockERC20Deposit creates a mock deposit event for the bridge +func CreateMockERC20Deposit(user, token, amount string) []byte { + return []byte(fmt.Sprintf(`{"user":"%s","token":"%s","amount":"%s","type":"deposit"}`, user, token, amount)) +} + +// CreateMockERC20Withdrawal creates a mock withdrawal event for the bridge +func CreateMockERC20Withdrawal(user, token, amount string) []byte { + return []byte(fmt.Sprintf(`{"user":"%s","token":"%s","amount":"%s","type":"withdrawal"}`, user, token, amount)) +} + +// MockERC20Listener creates a mock listener for testing ERC-20 bridge functionality +func MockERC20Listener(expectedEvents []string) listeners.ListenFunc { + return func(ctx context.Context, service *common.Service, eventstore listeners.EventStore) error { + // Simulate receiving events + for _, eventType := range expectedEvents { + select { + case <-ctx.Done(): + return ctx.Err() + default: + mockData := CreateMockERC20Transfer("0x123", "0x456", "1000000000000000000") + if err := eventstore.Broadcast(ctx, eventType, mockData); err != nil { + return fmt.Errorf("failed to broadcast event: %w", err) + } + } + } + + // Wait for context cancellation + <-ctx.Done() + return ctx.Err() + } +} + +// MockERC20DepositListener creates a mock listener that simulates deposit events +func MockERC20DepositListener(userAddress, tokenAddress, amount string) listeners.ListenFunc { + return func(ctx context.Context, service *common.Service, eventstore listeners.EventStore) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + mockData := CreateMockERC20Deposit(userAddress, tokenAddress, amount) + if err := eventstore.Broadcast(ctx, "erc20_deposit", mockData); err != nil { + return fmt.Errorf("failed to broadcast deposit event: %w", err) + } + } + + // Wait for context cancellation + <-ctx.Done() + return ctx.Err() + } +} + +// MockERC20WithdrawalListener creates a mock listener that simulates withdrawal events +func MockERC20WithdrawalListener(userAddress, tokenAddress, amount string) listeners.ListenFunc { + return func(ctx context.Context, service *common.Service, eventstore listeners.EventStore) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + mockData := CreateMockERC20Withdrawal(userAddress, tokenAddress, amount) + if err := eventstore.Broadcast(ctx, "erc20_withdrawal", mockData); err != nil { + return fmt.Errorf("failed to broadcast withdrawal event: %w", err) + } + } + + // Wait for context cancellation + <-ctx.Done() + return ctx.Err() + } +} diff --git a/tests/streams/utils/erc20_bridge_test.go b/tests/streams/utils/erc20_bridge_test.go new file mode 100644 index 000000000..8ee2c02d4 --- /dev/null +++ b/tests/streams/utils/erc20_bridge_test.go @@ -0,0 +1,60 @@ +// Package testutils provides ERC-20 bridge testing utilities. +// +// This file contains test-specific utilities for ERC-20 bridge testing. +// All core functionality is implemented in utils.go for better organization. +// +// Key features: +// - Mock ERC-20 event generation +// - Test-specific listener implementations +// - ERC-20 bridge testing helpers +package testutils + +import ( + "context" + "fmt" + + "github.com/trufnetwork/kwil-db/common" + "github.com/trufnetwork/kwil-db/extensions/listeners" + "github.com/trufnetwork/node/tests/streams/utils/erc20" +) + +// Note: Core mock functions are defined in utils.go for better organization +// These functions are available via the testutils package + +// MockERC20DepositListener creates a mock listener that simulates deposit events +func MockERC20DepositListener(userAddress, tokenAddress, amount string) listeners.ListenFunc { + return func(ctx context.Context, service *common.Service, eventstore listeners.EventStore) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + mockData := erc20.CreateMockERC20Deposit(userAddress, tokenAddress, amount) + if err := eventstore.Broadcast(ctx, "erc20_deposit", mockData); err != nil { + return fmt.Errorf("failed to broadcast deposit event: %w", err) + } + } + + // Wait for context cancellation + <-ctx.Done() + return ctx.Err() + } +} + +// MockERC20WithdrawalListener creates a mock listener that simulates withdrawal events +func MockERC20WithdrawalListener(userAddress, tokenAddress, amount string) listeners.ListenFunc { + return func(ctx context.Context, service *common.Service, eventstore listeners.EventStore) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + mockData := erc20.CreateMockERC20Withdrawal(userAddress, tokenAddress, amount) + if err := eventstore.Broadcast(ctx, "erc20_withdrawal", mockData); err != nil { + return fmt.Errorf("failed to broadcast withdrawal event: %w", err) + } + } + + // Wait for context cancellation + <-ctx.Done() + return ctx.Err() + } +} diff --git a/tests/streams/utils/eventstore/mock.go b/tests/streams/utils/eventstore/mock.go new file mode 100644 index 000000000..20d06865b --- /dev/null +++ b/tests/streams/utils/eventstore/mock.go @@ -0,0 +1,100 @@ +// Package eventstore provides mock implementations of event stores for testing +package eventstore + +import ( + "context" + "fmt" + "time" + + "github.com/trufnetwork/kwil-db/extensions/listeners" +) + +// mockEventStore implements listeners.EventStore for testing +type mockEventStore struct { + events chan []byte + kvStore map[string][]byte + broadcast []broadcastEvent +} + +type broadcastEvent struct { + eventType string + data []byte + timestamp time.Time +} + +// NewMockEventStore creates a new mock event store for testing +func NewMockEventStore() listeners.EventStore { + return &mockEventStore{ + events: make(chan []byte, 100), + kvStore: make(map[string][]byte), + broadcast: make([]broadcastEvent, 0), + } +} + +// Broadcast implements listeners.EventStore +func (m *mockEventStore) Broadcast(ctx context.Context, eventType string, data []byte) error { + m.broadcast = append(m.broadcast, broadcastEvent{ + eventType: eventType, + data: data, + timestamp: time.Now(), + }) + + // Also send to events channel for waiting + select { + case m.events <- data: + default: + // Channel full, skip + } + + return nil +} + +// Set implements listeners.EventKV +func (m *mockEventStore) Set(ctx context.Context, key []byte, value []byte) error { + m.kvStore[string(key)] = value + return nil +} + +// Get implements listeners.EventKV +func (m *mockEventStore) Get(ctx context.Context, key []byte) ([]byte, error) { + value, exists := m.kvStore[string(key)] + if !exists { + return nil, fmt.Errorf("key not found") + } + return value, nil +} + +// Delete implements listeners.EventKV +func (m *mockEventStore) Delete(ctx context.Context, key []byte) error { + delete(m.kvStore, string(key)) + return nil +} + +// WaitForEvent waits for a specific event type to be broadcast +func (m *mockEventStore) WaitForEvent(eventType string, timeout time.Duration) error { + timeoutCtx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + for { + select { + case <-timeoutCtx.Done(): + return fmt.Errorf("timeout waiting for event %s", eventType) + default: + // Check if event was already broadcast + for _, event := range m.broadcast { + if event.eventType == eventType { + return nil + } + } + time.Sleep(10 * time.Millisecond) + } + } +} + +// GetBroadcastEvents returns all broadcast events +func (m *mockEventStore) GetBroadcastEvents() []broadcastEvent { + return m.broadcast +} + +// MockEventStore is a type alias for backward compatibility +type MockEventStore = mockEventStore diff --git a/tests/streams/utils/example_test.go b/tests/streams/utils/example_test.go new file mode 100644 index 000000000..7d38e6821 --- /dev/null +++ b/tests/streams/utils/example_test.go @@ -0,0 +1,79 @@ +package testutils + +import ( + "testing" + + kwilTesting "github.com/trufnetwork/kwil-db/testing" +) + +func TestExampleERC20BridgeTesting(t *testing.T) { + // Example of how to use the reorganized ERC-20 bridge testing utilities + + // Setup ERC-20 bridge configuration + erc20Config := NewERC20BridgeConfig(). + WithRPC("sepolia", "wss://your-sepolia-provider.com"). + WithSigner("test_bridge", "/path/to/signer/key"). + WithAutoStart() + + // Create test options + opts := GetTestOptionsWithERC20Bridge(erc20Config) + + // Define your schema test + schemaTest := kwilTesting.SchemaTest{ + Name: "ERC-20 Bridge Test", + // ... your test cases + } + + // Note: In a real test, you would call RunSchemaTest(t, schemaTest, opts) + // For this example, we just verify the configuration is valid + _ = schemaTest + _ = opts +} + +func TestExampleMockERC20BridgeTesting(t *testing.T) { + // Example of mock testing + mockListener := MockERC20Listener([]string{"erc20_transfer"}) + erc20Config := MockERC20Bridge(mockListener) + opts := GetTestOptionsWithERC20Bridge(erc20Config) + + // Use in tests + _ = opts +} + +func TestExampleCombinedCacheAndERC20Testing(t *testing.T) { + // Example of using both cache and ERC-20 bridge + cacheOpts := SimpleCache("0x123...", "stream1") + erc20Opts := SimpleERC20Bridge("wss://rpc.com", "/key/path") + opts := GetTestOptionsWithBoth(cacheOpts, erc20Opts) + + // Use in tests + _ = opts +} + +func TestReorganizationWorks(t *testing.T) { + // This test verifies that the reorganized utilities compile and work + + // Test cache options + cacheOpts := NewCacheOptions(). + WithEnabled(). + WithStream("test_provider", "test_stream", "0 * * * *") + + if !cacheOpts.IsEnabled() { + t.Error("Cache should be enabled") + } + + // Test ERC-20 bridge options + erc20Opts := NewERC20BridgeConfig(). + WithRPC("test_chain", "wss://test-rpc.com"). + WithSigner("test_signer", "/test/key") + + if erc20Opts.RPC["test_chain"] != "wss://test-rpc.com" { + t.Error("RPC URL should be set") + } + + // Test combined options + combinedOpts := GetTestOptionsWithBoth(cacheOpts, erc20Opts) + if combinedOpts.Cache == nil || combinedOpts.ERC20Bridge == nil { + t.Error("Combined options should have both cache and ERC-20 bridge") + } +} diff --git a/tests/streams/utils/runner.go b/tests/streams/utils/runner.go new file mode 100644 index 000000000..25a476f88 --- /dev/null +++ b/tests/streams/utils/runner.go @@ -0,0 +1,222 @@ +// Package testutils provides the main test orchestration logic for cache and ERC-20 bridge testing +package testutils + +import ( + "context" + "fmt" + "testing" + "time" + + kwilTesting "github.com/trufnetwork/kwil-db/testing" + + // Import extensions for registration + "github.com/trufnetwork/node/extensions/tn_cache" + "github.com/trufnetwork/node/tests/streams/utils/cache" + "github.com/trufnetwork/node/tests/streams/utils/erc20" + "github.com/trufnetwork/node/tests/streams/utils/service" +) + +// Options extends kwilTesting.Options with both cache and ERC-20 bridge configuration +type Options struct { + *kwilTesting.Options + Cache *cache.CacheOptions + ERC20Bridge *erc20.ERC20BridgeConfig + DisableCache bool // Explicitly disable cache (overrides default) + DisableERC20Bridge bool // Explicitly disable ERC-20 bridge (overrides default) +} + +// RunSchemaTest is a wrapper around kwilTesting.RunSchemaTest that automatically +// handles both cache and ERC-20 bridge setup. +func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { + // Convert to kwilTesting.Options - use type assertion since TestingT is an alias for testing.T + testT := t.(*testing.T) + kwilOpts := &kwilTesting.Options{ + UseTestContainer: true, + Logger: testT, + SetupMetaStore: true, // Enable kwild_chain schema for blockchain height testing + InitialHeight: 1, // Set initial blockchain height for tests (match production default) + } + if options != nil && options.Options != nil { + kwilOpts = options.Options + // Ensure meta store is always enabled for cache extension tests + kwilOpts.SetupMetaStore = true + if kwilOpts.InitialHeight <= 0 { + kwilOpts.InitialHeight = 1 // Default test height matches production + } + } + + // Handle cache configuration + var cacheConfig *cache.CacheOptions + if options == nil || (options.Cache == nil && !options.DisableCache) { + // Fallback: Enabled, no auto-refresh for tests + cacheConfig = cache.NewCacheOptions(). + WithEnabled(). + WithMaxBlockAge(-1 * time.Second). // Disable sync check + WithResolutionSchedule("0 0 31 2 *") // Never auto-resolve + } else if options.Cache != nil { + cacheConfig = options.Cache + } + + // Handle ERC-20 bridge configuration + var erc20Config *erc20.ERC20BridgeConfig + if options != nil && options.ERC20Bridge != nil && !options.DisableERC20Bridge { + erc20Config = options.ERC20Bridge + } + + // Create wrapped function tests + wrappedTests := s.FunctionTests + if cacheConfig != nil || erc20Config != nil { + wrappedTests = wrapWithExtensionsSetup(context.Background(), s.FunctionTests, cacheConfig, erc20Config, kwilOpts) + } + + // Run with wrapper + kwilTesting.RunSchemaTest(testT, kwilTesting.SchemaTest{ + Name: s.Name, + SeedScripts: s.SeedScripts, + FunctionTests: wrappedTests, + }, kwilOpts) +} + +// wrapWithExtensionsSetup wraps test functions with both cache and ERC-20 bridge initialization +func wrapWithExtensionsSetup(ctx context.Context, originalFuncs []kwilTesting.TestFunc, cacheConfig *cache.CacheOptions, erc20Config *erc20.ERC20BridgeConfig, opts *kwilTesting.Options) []kwilTesting.TestFunc { + wrapped := make([]kwilTesting.TestFunc, len(originalFuncs)) + for i, fn := range originalFuncs { + originalFn := fn + wrapped[i] = func(ctx context.Context, platform *kwilTesting.Platform) (testErr error) { + // Collect cleanup functions to run after the test completes + var cleanups []func() + + // Setup cache extension if configured + if cacheConfig != nil && cacheConfig.IsEnabled() { + cleanup, err := setupCacheExtension(ctx, cacheConfig, platform) + if err != nil { + testErr = fmt.Errorf("failed to setup cache extension: %w", err) + return + } + cleanups = append(cleanups, cleanup) + } + + // Setup ERC-20 bridge if configured + if erc20Config != nil { + cleanup, err := setupERC20Bridge(ctx, erc20Config, platform) + if err != nil { + testErr = fmt.Errorf("failed to setup ERC-20 bridge: %w", err) + return + } + cleanups = append(cleanups, cleanup) + } + + // Run original test + err := originalFn(ctx, platform) + if err != nil { + testErr = err + } + + // Run cleanups in reverse order + for i := len(cleanups) - 1; i >= 0; i-- { + if cleanups[i] != nil { + cleanups[i]() + } + } + + return + } + } + return wrapped +} + +// setupCacheExtension sets up the cache extension for testing +func setupCacheExtension(ctx context.Context, cacheConfig *cache.CacheOptions, platform *kwilTesting.Platform) (func(), error) { + // Setup basic cache test + helper := cache.SetupCacheTest(ctx, platform, cacheConfig) + + // Create mock service for extension setup + mockService := service.CreateDefaultService() + + // Parse and setup extension + processedConfig, err := tn_cache.ParseConfig(mockService) + if err != nil { + return nil, fmt.Errorf("failed to parse cache config: %w", err) + } + + ext, err := tn_cache.SetupCacheExtension(ctx, processedConfig, platform.Engine, mockService) + if err != nil { + return nil, fmt.Errorf("failed to setup cache extension: %w", err) + } + + // Register extension for cleanup + tn_cache.SetExtension(ext) + + cleanup := func() { + // Cleanup helper first + helper.Cleanup() + // Stop background tasks + if ext.Scheduler() != nil { + if stopErr := ext.Scheduler().Stop(); stopErr != nil { + mockService.Logger.Error("failed to stop scheduler", "error", stopErr) + } + } + if ext.SyncChecker() != nil { + ext.SyncChecker().Stop() + } + ext.Close() + tn_cache.SetExtension(nil) + } + + return cleanup, nil +} + +// setupERC20Bridge sets up the ERC-20 bridge for testing +func setupERC20Bridge(ctx context.Context, erc20Config *erc20.ERC20BridgeConfig, platform *kwilTesting.Platform) (func(), error) { + // Setup ERC-20 bridge test + bridgeHelper, err := erc20.SetupERC20BridgeTest(ctx, platform, erc20Config) + if err != nil { + return nil, fmt.Errorf("failed to setup ERC-20 bridge test: %w", err) + } + + // Auto-start listener if configured + if erc20Config.AutoStart { + if startErr := bridgeHelper.StartERC20Listener(ctx); startErr != nil { + return nil, fmt.Errorf("failed to start ERC-20 listener: %w", startErr) + } + } + + cleanup := func() { + _ = bridgeHelper.Cleanup() + } + + return cleanup, nil +} + +// TestingT interface for test functions +type TestingT interface { + Fatalf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) +} + +// WithExtensions is a convenience wrapper for both cache and ERC-20 bridge testing +func WithExtensions(t TestingT, ctx context.Context, platform *kwilTesting.Platform, cacheConfig *cache.CacheOptions, erc20Config *erc20.ERC20BridgeConfig, testFunc func(*cache.CacheTestHelper, *erc20.ERC20BridgeTestHelper)) { + // Setup cache if configured + var cacheHelper *cache.CacheTestHelper + if cacheConfig != nil { + cacheHelper = cache.SetupCacheTest(ctx, platform, cacheConfig) + defer cacheHelper.Cleanup() + } + + // Setup ERC-20 bridge if configured + var bridgeHelper *erc20.ERC20BridgeTestHelper + if erc20Config != nil { + var err error + bridgeHelper, err = erc20.SetupERC20BridgeTest(ctx, platform, erc20Config) + if err != nil { + t.Fatalf("Failed to setup ERC-20 bridge: %v", err) + } + defer func() { + if err := bridgeHelper.Cleanup(); err != nil { + t.Errorf("Failed to cleanup ERC-20 bridge: %v", err) + } + }() + } + + testFunc(cacheHelper, bridgeHelper) +} diff --git a/tests/streams/utils/service/service.go b/tests/streams/utils/service/service.go new file mode 100644 index 000000000..f7816ac96 --- /dev/null +++ b/tests/streams/utils/service/service.go @@ -0,0 +1,43 @@ +// Package service provides helpers for creating common.Service instances for testing +package service + +import ( + "github.com/trufnetwork/kwil-db/common" + "github.com/trufnetwork/kwil-db/config" + "github.com/trufnetwork/kwil-db/core/log" +) + +// CreateBridgeService creates a service with ERC-20 bridge configuration +func CreateBridgeService(bridgeConfig *config.ERC20BridgeConfig) (*common.Service, error) { + // Create full config + fullConfig := &config.Config{ + Erc20Bridge: *bridgeConfig, + } + + // Create service + service := &common.Service{ + Logger: log.NewStdoutLogger(), + LocalConfig: fullConfig, + Identity: []byte("test-erc20-node"), + } + + return service, nil +} + +// CreateServiceWithLocalConfig creates a service with custom local configuration +func CreateServiceWithLocalConfig(localConfig *config.Config) *common.Service { + return &common.Service{ + Logger: log.NewStdoutLogger(), + LocalConfig: localConfig, + Identity: []byte("test-node"), + } +} + +// CreateDefaultService creates a basic service for testing +func CreateDefaultService() *common.Service { + return &common.Service{ + Logger: log.NewStdoutLogger(), + LocalConfig: config.DefaultConfig(), + Identity: []byte("test-node"), + } +} diff --git a/tests/streams/utils/utils.go b/tests/streams/utils/utils.go index 878fd5e61..84804eb9f 100644 --- a/tests/streams/utils/utils.go +++ b/tests/streams/utils/utils.go @@ -1,22 +1,22 @@ +// Package testutils provides utilities for testing Kwil schemas and extensions. +// This package maintains backward compatibility while organizing functionality into focused subpackages. +// +// For cache testing: see cache package +// For ERC-20 bridge testing: see erc20 package +// For combined testing: see runner package package testutils import ( - "context" - "encoding/json" - "fmt" - "strconv" - "testing" "time" - "github.com/trufnetwork/kwil-db/common" - "github.com/trufnetwork/kwil-db/config" - "github.com/trufnetwork/kwil-db/core/log" + "github.com/trufnetwork/kwil-db/extensions/listeners" "github.com/trufnetwork/kwil-db/extensions/precompiles" - "github.com/trufnetwork/kwil-db/node/types/sql" kwilTesting "github.com/trufnetwork/kwil-db/testing" - // make sure the extension is loaded + // Extension registration "github.com/trufnetwork/node/extensions/tn_cache" + "github.com/trufnetwork/node/tests/streams/utils/cache" + "github.com/trufnetwork/node/tests/streams/utils/erc20" ) // init registers tn_cache precompiles globally for tests @@ -27,199 +27,42 @@ func init() { } } -func Ptr[T any](v T) *T { - return &v -} - -// CacheOptions provides a fluent API for configuring the tn_cache extension -type CacheOptions struct { - enabled bool - resolutionSchedule string - maxBlockAge string - streams []StreamConfig -} - -// StreamConfig represents a single stream to cache -type StreamConfig struct { - DataProvider string `json:"data_provider"` - StreamID string `json:"stream_id"` - CronSchedule string `json:"cron_schedule"` - From *int64 `json:"from,omitempty"` - IncludeChildren bool `json:"include_children,omitempty"` -} - -// NewCacheOptions creates a new cache configuration with defaults -func NewCacheOptions() *CacheOptions { - return &CacheOptions{ - enabled: false, // Default to disabled - } -} - -// WithEnabled enables the cache extension -func (c *CacheOptions) WithEnabled() *CacheOptions { - c.enabled = true - return c -} - -// WithDisabled explicitly disables the cache extension (default) -func (c *CacheOptions) WithDisabled() *CacheOptions { - c.enabled = false - return c -} - -// WithResolutionSchedule sets the schedule for re-resolving wildcards (default: daily at midnight) -func (c *CacheOptions) WithResolutionSchedule(schedule string) *CacheOptions { - c.resolutionSchedule = schedule - return c -} - -// WithMaxBlockAge sets the maximum age of blocks to consider the node synced -func (c *CacheOptions) WithMaxBlockAge(duration time.Duration) *CacheOptions { - c.maxBlockAge = duration.String() - return c -} - -// WithStream adds a stream to cache -func (c *CacheOptions) WithStream(dataProvider, streamID, cronSchedule string) *CacheOptions { - c.streams = append(c.streams, StreamConfig{ - DataProvider: dataProvider, - StreamID: streamID, - CronSchedule: cronSchedule, - }) - return c -} +// ============================================================================ +// BACKWARD COMPATIBILITY RE-EXPORTS +// ============================================================================ -// WithStreamFromTime adds a stream with a specific start time -func (c *CacheOptions) WithStreamFromTime(dataProvider, streamID, cronSchedule string, fromTime int64) *CacheOptions { - c.streams = append(c.streams, StreamConfig{ - DataProvider: dataProvider, - StreamID: streamID, - CronSchedule: cronSchedule, - From: &fromTime, - }) - return c -} - -// WithComposedStream adds a composed stream with children included -func (c *CacheOptions) WithComposedStream(dataProvider, streamID, cronSchedule string, includeChildren bool) *CacheOptions { - c.streams = append(c.streams, StreamConfig{ - DataProvider: dataProvider, - StreamID: streamID, - CronSchedule: cronSchedule, - IncludeChildren: includeChildren, - }) - return c -} +// Type Aliases for backward compatibility +type CacheOptions = cache.CacheOptions +type StreamConfig = cache.StreamConfig +type ERC20BridgeConfig = erc20.ERC20BridgeConfig +type ERC20ChainConfig = erc20.ERC20ChainConfig +type ERC20BridgeTestHelper = erc20.ERC20BridgeTestHelper +type CacheTestHelper = cache.CacheTestHelper -// WithWildcardProvider adds all streams from a provider -func (c *CacheOptions) WithWildcardProvider(dataProvider, cronSchedule string) *CacheOptions { - c.streams = append(c.streams, StreamConfig{ - DataProvider: dataProvider, - StreamID: "*", - CronSchedule: cronSchedule, - }) - return c -} +// ============================================================================ +// CACHE FUNCTIONS (RE-EXPORTS) +// ============================================================================ -// Build converts the options into the metadata map for the extension -func (c *CacheOptions) Build() map[string]string { - metadata := make(map[string]string) +// Cache configuration builders +func NewCacheOptions() *CacheOptions { return cache.NewCacheOptions() } - // Always set enabled status - metadata["enabled"] = strconv.FormatBool(c.enabled) +// ============================================================================ +// ERC-20 FUNCTIONS (RE-EXPORTS) +// ============================================================================ - // Only add other options if cache is enabled - if c.enabled { - if c.resolutionSchedule != "" { - metadata["resolution_schedule"] = c.resolutionSchedule - } +// ERC-20 configuration builders +func NewERC20BridgeConfig() *ERC20BridgeConfig { return erc20.NewERC20BridgeConfig() } - if c.maxBlockAge != "" { - metadata["max_block_age"] = c.maxBlockAge - } +// ============================================================================ +// CONVENIENCE FUNCTIONS +// ============================================================================ - if len(c.streams) > 0 { - // Convert streams to JSON - streamsJSON, err := json.Marshal(c.streams) - if err != nil { - panic(fmt.Sprintf("failed to marshal cache streams: %v", err)) - } - metadata["streams_inline"] = string(streamsJSON) - } - } - - return metadata -} - -// Options extends kwilTesting.Options with cache configuration -type Options struct { - *kwilTesting.Options - Cache *CacheOptions - DisableCache bool // Explicitly disable cache (overrides default) -} - -// Common cache configurations - -// DisabledCache returns cache options with cache disabled (default) -func DisabledCache() *CacheOptions { - return NewCacheOptions() -} - -// SimpleCache returns cache options with basic hourly caching -func SimpleCache(dataProvider, streamID string) *CacheOptions { - return NewCacheOptions(). - WithEnabled(). - WithStream(dataProvider, streamID, "0 0 * * * *") // Hourly -} - -// TestCache returns cache options suitable for testing with manual refresh -func TestCache(dataProvider, streamID string) *CacheOptions { - return NewCacheOptions(). - WithEnabled(). - WithMaxBlockAge(-1*time.Second). // Disable sync checking for tests - WithStream(dataProvider, streamID, "0 0 31 2 *") // Only on Feb 31st (never happens) -} - -// ProductionCache returns cache options suitable for production with daily updates -func ProductionCache() *CacheOptions { - return NewCacheOptions(). - WithEnabled(). - WithMaxBlockAge(1 * time.Hour). // Default 1 hour - WithResolutionSchedule("0 0 * * *") // Re-resolve daily at midnight -} - -// Example usage: -// -// Basic cache disabled (default): -// testutils.GetTestOptions() -// -// Simple cache for one stream: -// testutils.GetTestOptions(testutils.SimpleCache("0x123...", "stream1")) -// -// Complex cache configuration: -// cache := testutils.NewCacheOptions(). -// WithEnabled(). -// WithMaxBlockAge(30 * time.Minute). -// WithStream("0x123...", "stream1", "0 * * * *"). // Hourly -// WithStreamFromTime("0x456...", "stream2", "*/30 * * * *", 1700000000). // Every 30 min from timestamp -// WithComposedStream("0x789...", "composed1", "0 0 * * *", true). // Daily with children -// WithWildcardProvider("0xabc...", "0 */6 * * *") // All streams every 6 hours -// testutils.GetTestOptions(cache) - -// GetTestOptions returns the common test options with optional cache configuration -// By default, cache is DISABLED to maintain backward compatibility -// -// DEPRECATED: This returns *kwilTesting.Options for backward compatibility. -// New tests should use GetTestOptionsWithCache() or the testutils.RunSchemaTest wrapper. -func GetTestOptions(cacheOpts ...*CacheOptions) *kwilTesting.Options { - // For backward compatibility, return kwilTesting.Options - // Cache configuration is ignored when using kwilTesting.RunSchemaTest directly +func GetTestOptions() *kwilTesting.Options { return &kwilTesting.Options{ UseTestContainer: true, } } -// GetTestOptionsWithCache returns the extended options for use with testutils.RunSchemaTest func GetTestOptionsWithCache(cacheOpts ...*CacheOptions) *Options { opts := &Options{ Options: &kwilTesting.Options{ @@ -227,7 +70,6 @@ func GetTestOptionsWithCache(cacheOpts ...*CacheOptions) *Options { }, } - // If cache options are provided, enable cache with those options if len(cacheOpts) > 0 && cacheOpts[0] != nil { opts.Cache = cacheOpts[0] opts.DisableCache = false @@ -236,243 +78,65 @@ func GetTestOptionsWithCache(cacheOpts ...*CacheOptions) *Options { return opts } -// CacheTestHelper provides utilities for cache-enabled tests -type CacheTestHelper struct { - cacheConfig *CacheOptions - platform *kwilTesting.Platform - *tn_cache.TestHelper // Embedded core helper -} - -// SetupCacheTest is a helper to set up cache configuration and test DB injection -// It should be called at the beginning of your test function -func SetupCacheTest(ctx context.Context, platform *kwilTesting.Platform, cacheConfig *CacheOptions) *CacheTestHelper { - // Set up test database configuration (uses test container defaults) - tn_cache.SetTestDBConfiguration(config.DBConfig{ - Host: "localhost", - Port: "52853", // Default test container port - User: "kwild", - Pass: "kwild", - DBName: "kwil_test_db", - }) - - // Inject the test's database connection - tn_cache.SetTestDB(platform.DB) - - // Configure the extension - configMap := make(map[string]string) - for k, v := range cacheConfig.Build() { - configMap[k] = fmt.Sprintf("%v", v) - } - tn_cache.SetTestConfiguration(configMap) - - helper := &CacheTestHelper{ - cacheConfig: cacheConfig, - platform: platform, - } - helper.TestHelper = tn_cache.GetTestHelper() - return helper -} - -// Cleanup should be called with defer to clean up test injection -func (h *CacheTestHelper) Cleanup() { - tn_cache.SetTestDB(nil) -} - -// IsCacheEnabled returns whether the cache is enabled -func (h *CacheTestHelper) IsCacheEnabled() bool { - return h.cacheConfig.enabled -} - -// GetDB returns the test database for direct queries -func (h *CacheTestHelper) GetDB() sql.DB { - return h.platform.DB -} - -// SetDB sets the test database for direct queries -func (h *CacheTestHelper) SetDB(db sql.DB) { - h.platform.DB = db - tn_cache.SetTestDB(db) - - // Also ensure the scheduler uses the correct namespace for tests - // In tests, the seed scripts create actions without namespace prefix, - // which means they go into the test database's default namespace. - // Since Engine.Call with empty string looks in "main", but tests might not have "main", - // we need to figure out the right namespace. - // - // For now, we'll keep the default empty namespace and let the resolution - // be retried when TriggerStreamResolution is called. - // The initial resolution failure is logged but not fatal. -} - -// WithCache is a test wrapper that sets up and tears down cache configuration -// Usage: -// -// testutils.WithCache(t, ctx, platform, cacheConfig, func(cache *CacheTestHelper) { -// // Your test code here -// }) -func WithCache(t *testing.T, ctx context.Context, platform *kwilTesting.Platform, cacheConfig *CacheOptions, testFunc func(*CacheTestHelper)) { - helper := SetupCacheTest(ctx, platform, cacheConfig) - defer helper.Cleanup() - testFunc(helper) -} - -// DefaultOptions returns options with cache enabled by default -func DefaultOptions() *Options { +func GetTestOptionsWithERC20Bridge(erc20Opts *ERC20BridgeConfig) *Options { return &Options{ Options: &kwilTesting.Options{ UseTestContainer: true, }, - // Cache is enabled by default with no specific configuration + ERC20Bridge: erc20Opts, } } -// OptionsWithCache returns options with a specific cache configuration -func OptionsWithCache(cache *CacheOptions) *Options { - return &Options{ - Options: &kwilTesting.Options{ - UseTestContainer: true, - }, - Cache: cache, - } -} - -// OptionsWithoutCache returns options with cache explicitly disabled -func OptionsWithoutCache() *Options { - return &Options{ +func GetTestOptionsWithBoth(cacheOpts *CacheOptions, erc20Opts *ERC20BridgeConfig) *Options { + opts := &Options{ Options: &kwilTesting.Options{ UseTestContainer: true, }, - DisableCache: true, } -} - -// RunInTx runs the provided test function within a database transaction. -// It automatically sets the cache DB to the transaction DB before running the function, -// then restores the original DB after. This ensures cache operations see uncommitted changes -// made within the transaction (e.g., newly created streams). -// -// Usage: -// -// helper.RunInTx(t, func(t *testing.T, txPlatform *kwilTesting.Platform) { -// // Your test code here - create streams, trigger resolution, etc. -// }) -func (h *CacheTestHelper) RunInTx(t *testing.T, testFn func(t *testing.T, txPlatform *kwilTesting.Platform)) { - WithTx(h.platform, func(t *testing.T, txPlatform *kwilTesting.Platform) { - // Save original DB and set tx DB - originalDB := h.GetDB() - h.SetDB(txPlatform.DB) - defer h.SetDB(originalDB) // Restore after - - testFn(t, txPlatform) - })(t) -} -// RunSchemaTest is a wrapper around kwilTesting.RunSchemaTest that automatically -// handles cache setup. By default, cache is enabled with a test fallback. -func RunSchemaTest(t *testing.T, s kwilTesting.SchemaTest, options *Options) { - // Convert to kwilTesting.Options - kwilOpts := &kwilTesting.Options{ - UseTestContainer: true, - Logger: t, - SetupMetaStore: true, // Enable kwild_chain schema for blockchain height testing - InitialHeight: 1, // Set initial blockchain height for tests (match production default) - } - if options != nil && options.Options != nil { - kwilOpts = options.Options - // Ensure meta store is always enabled for cache extension tests - kwilOpts.SetupMetaStore = true - if kwilOpts.InitialHeight <= 0 { - kwilOpts.InitialHeight = 1 // Default test height matches production - } + if cacheOpts != nil { + opts.Cache = cacheOpts + opts.DisableCache = false } - // Default to enabled with fallback (as original schema.go) - var cacheConfig *CacheOptions - if options == nil || (options.Cache == nil && !options.DisableCache) { - // Fallback: Enabled, no auto-refresh for tests - cacheConfig = NewCacheOptions(). - WithEnabled(). - WithMaxBlockAge(-1 * time.Second). // Disable sync check - WithResolutionSchedule("0 0 31 2 *") // Never auto-resolve - } else if options != nil && options.Cache != nil { - cacheConfig = options.Cache + if erc20Opts != nil { + opts.ERC20Bridge = erc20Opts + opts.DisableERC20Bridge = false } - // Run with wrapper - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: s.Name, - SeedScripts: s.SeedScripts, - FunctionTests: wrapWithCacheSetup(context.Background(), s.FunctionTests, cacheConfig, kwilOpts), - }, kwilOpts) + return opts } -// wrapWithCacheSetup wraps test functions with full cache initialization -func wrapWithCacheSetup(ctx context.Context, originalFuncs []kwilTesting.TestFunc, cacheConfig *CacheOptions, opts *kwilTesting.Options) []kwilTesting.TestFunc { - if cacheConfig == nil || !cacheConfig.enabled { - return originalFuncs - } - - wrapped := make([]kwilTesting.TestFunc, len(originalFuncs)) - for i, fn := range originalFuncs { - originalFn := fn - wrapped[i] = func(ctx context.Context, platform *kwilTesting.Platform) (testErr error) { - // Partial setup (DB config, injection, config map) - helper := SetupCacheTest(ctx, platform, cacheConfig) - defer helper.Cleanup() - - // Full init (restore original schema.go logic) - mockService := &common.Service{ - Logger: log.NewStdoutLogger(), - LocalConfig: &config.Config{ - DB: config.DBConfig{ - Host: "localhost", - Port: "52853", - User: "kwild", - Pass: "kwild", - DBName: "kwil_test_db", - }, - Extensions: map[string]map[string]string{ - tn_cache.ExtensionName: cacheConfig.Build(), - }, - }, - } - - processedConfig, err := tn_cache.ParseConfig(mockService) - if err != nil { - testErr = fmt.Errorf("failed to parse config: %w", err) - return - } - - ext, err := tn_cache.SetupCacheExtension(ctx, processedConfig, platform.Engine, mockService) - if err != nil { - testErr = fmt.Errorf("failed to setup extension: %w", err) - return - } - tn_cache.SetExtension(ext) - defer tn_cache.SetExtension(nil) // Cleanup +func SimpleCache(dataProvider, streamID string) *CacheOptions { + return NewCacheOptions(). + WithEnabled(). + WithMaxBlockAge(-1*time.Second). // Disable sync checking for tests + WithResolutionSchedule("0 0 31 2 *"). // Never auto-resolve (Feb 31st) + WithStream(dataProvider, streamID, "0 0 31 2 *") // Never auto-refresh (Feb 31st) +} - // Run original test - err = originalFn(ctx, platform) - if err != nil { - testErr = err - return - } +func SimpleERC20Bridge(rpcURL, signerKey string) *ERC20BridgeConfig { + return NewERC20BridgeConfig(). + WithRPC("sepolia", rpcURL). + WithSigner("test_bridge", signerKey) +} - // Stop the scheduler to clean up background tasks - if ext.Scheduler() != nil { - if stopErr := ext.Scheduler().Stop(); stopErr != nil { - mockService.Logger.Error("failed to stop scheduler after test", "error", stopErr) - } - } +func MockERC20Bridge(mockListener listeners.ListenFunc) *ERC20BridgeConfig { + return NewERC20BridgeConfig(). + WithRPC("mock", "ws://mock-rpc"). + WithSigner("mock_bridge", "/dev/null"). + WithMockListener("evm_sync", mockListener) +} - if ext.SyncChecker() != nil { - ext.SyncChecker().Stop() - } +// MockERC20Listener creates a mock listener for testing ERC-20 bridge functionality +func MockERC20Listener(expectedEvents []string) listeners.ListenFunc { + return erc20.MockERC20Listener(expectedEvents) +} - defer ext.Close() +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ - return - } - } - return wrapped +func Ptr[T any](v T) *T { + return &v } From d6114a694acedbc95dba4fd3c93e1aaa17fe0ae5 Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Tue, 9 Sep 2025 08:47:33 -0300 Subject: [PATCH 02/18] feat(tests): enhance ERC-20 bridge tests with mock listener and path resolution This commit introduces significant improvements to the ERC-20 bridge testing framework, including: - Updated `TestERC20BridgeSimpleBalance` to compute the absolute path for the seed script dynamically, ensuring compatibility across environments. - Added `TestERC20BridgeMockListener` to validate the ERC-20 bridge testing infrastructure using a mock listener, enhancing test coverage without requiring the actual `erc20_bridge` extension. - Improved comments to clarify the purpose and expected behavior of the tests, including a note on the current unavailability of the `erc20_bridge` extension in the test environment. These changes aim to strengthen the reliability and clarity of the ERC-20 bridge tests, facilitating easier maintenance and future enhancements. --- .../erc20/erc20_bridge_simple_test.go | 66 +++++++++++++++++-- tests/extensions/erc20/simple_mock.sql | 5 +- tests/streams/utils/erc20/helper.go | 18 +++-- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/tests/extensions/erc20/erc20_bridge_simple_test.go b/tests/extensions/erc20/erc20_bridge_simple_test.go index 2ebba39a9..1a34768b9 100644 --- a/tests/extensions/erc20/erc20_bridge_simple_test.go +++ b/tests/extensions/erc20/erc20_bridge_simple_test.go @@ -3,22 +3,35 @@ package tests import ( "context" "fmt" + "path/filepath" + "runtime" "testing" "github.com/stretchr/testify/require" "github.com/trufnetwork/kwil-db/common" kwilTesting "github.com/trufnetwork/kwil-db/testing" testutils "github.com/trufnetwork/node/tests/streams/utils" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" ) -// Minimal integration to validate that the ERC-20 bridge extension can be initialized -// via USE and that balance() can be called (expected 0 without any deposits). -// Seed script: tests/extensions/erc20/simple_mock.sql -// Reference: Kwil ERC-20 Bridge Usage (balance/bridge): https://docs.kwil.com/docs/erc20-bridge/usage#bridge +// TestERC20BridgeSimpleBalance validates ERC-20 bridge extension initialization and balance() call. +// This test requires the erc20_bridge extension to be available in the test environment. +// +// BLOCKER: The erc20_bridge extension is not currently available in the test environment. +// When this extension becomes available, this test will: +// 1. Execute the USE erc20_bridge statement from simple_mock.sql +// 2. Call the get_balance action which proxies to sepolia_bridge.balance() +// 3. Verify it returns "0" for a wallet with no deposit events +// +// Until then, this test will fail with a clear error about the missing extension. func TestERC20BridgeSimpleBalance(t *testing.T) { + // Compute absolute path to the seed script relative to this test file + _, thisFile, _, _ := runtime.Caller(0) + seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "erc20_bridge_simple_balance", - SeedScripts: []string{"./tests/extensions/erc20/simple_mock.sql"}, + SeedScripts: []string{seedPath}, // This will fail if erc20_bridge extension doesn't exist FunctionTests: []kwilTesting.TestFunc{ func(ctx context.Context, platform *kwilTesting.Platform) error { // Arbitrary wallet address to query (no prior deposits => expected 0) @@ -27,14 +40,13 @@ func TestERC20BridgeSimpleBalance(t *testing.T) { txCtx := &common.TxContext{ Ctx: ctx, BlockContext: &common.BlockContext{Height: 1}, - Signer: platform.Deployer, // signer identity; not material for a read-only balance + Signer: platform.Deployer, Caller: "0x0000000000000000000000000000000000000000", TxID: platform.Txid(), } engCtx := &common.EngineContext{TxContext: txCtx} // Call our seeded action which proxies to sepolia_bridge.balance($wallet) - // Created in simple_mock.sql var got string r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{wallet}, func(row *common.Row) error { if len(row.Values) != 1 { @@ -55,3 +67,43 @@ func TestERC20BridgeSimpleBalance(t *testing.T) { }, }, &testutils.Options{Options: testutils.GetTestOptions()}) } + +// TestERC20BridgeMockListener tests our ERC-20 bridge testing helpers with a mock listener. +// This doesn't require the real erc20_bridge extension but validates our test infrastructure. +func TestERC20BridgeMockListener(t *testing.T) { + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ + Name: "erc20_bridge_mock_listener", + FunctionTests: []kwilTesting.TestFunc{ + func(ctx context.Context, platform *kwilTesting.Platform) error { + // Create mock listener that broadcasts ERC-20 transfer events + mock := testutils.MockERC20Listener([]string{"erc20_transfer"}) + + // Configure ERC-20 bridge with mock listener + cfg := testutils.NewERC20BridgeConfig(). + WithRPC("sepolia", "wss://mock-sepolia.com"). + WithSigner("test_bridge", "/dev/null"). + WithMockListener("evm_sync", mock). + WithAutoStart() + + // Setup test environment + helper, err := testerc20.SetupERC20BridgeTest(ctx, platform, cfg) + require.NoError(t, err) + defer helper.Cleanup() + + // Start listener and verify it works + require.NoError(t, helper.StartERC20Listener(ctx)) + + // Verify listener is running + require.True(t, helper.IsListenerRunning(), "listener should be running") + + // Stop listener + require.NoError(t, helper.StopERC20Listener()) + + // Verify listener stopped + require.False(t, helper.IsListenerRunning(), "listener should have stopped") + + return nil + }, + }, + }, &testutils.Options{Options: testutils.GetTestOptions()}) +} diff --git a/tests/extensions/erc20/simple_mock.sql b/tests/extensions/erc20/simple_mock.sql index e6f314b1d..251c0d426 100644 --- a/tests/extensions/erc20/simple_mock.sql +++ b/tests/extensions/erc20/simple_mock.sql @@ -1,8 +1,11 @@ -- we'll use this to test configuration of the erc20 bridge in tests. -- if we successfully emit events and this listens, then we know the bridge is configured correctly. +USE kwil_ordered_sync as kwil_ordered_sync; +USE kwil_erc20_meta as kwil_erc20_meta; + USE erc20 { chain: 'sepolia', - escrow: '0x1111111111111111111111111111111111111111', + escrow: '0x1111111111111111111111111111111111111111' } AS sepolia_bridge; CREATE ACTION get_balance($wallet_address TEXT) public { diff --git a/tests/streams/utils/erc20/helper.go b/tests/streams/utils/erc20/helper.go index 56f45b0af..caf64921b 100644 --- a/tests/streams/utils/erc20/helper.go +++ b/tests/streams/utils/erc20/helper.go @@ -8,6 +8,9 @@ import ( "github.com/trufnetwork/kwil-db/common" "github.com/trufnetwork/kwil-db/extensions/listeners" + _ "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" + _ "github.com/trufnetwork/kwil-db/node/exts/evm-sync" + _ "github.com/trufnetwork/kwil-db/node/exts/ordered-sync" kwilTesting "github.com/trufnetwork/kwil-db/testing" "github.com/trufnetwork/node/tests/streams/utils/eventstore" "github.com/trufnetwork/node/tests/streams/utils/service" @@ -53,15 +56,16 @@ func (h *ERC20BridgeTestHelper) StartERC20Listener(ctx context.Context) error { return fmt.Errorf("listener already running") } - // Get the registered listener - listenerFunc, exists := listeners.GetListener("evm_sync") - if !exists { - return fmt.Errorf("evm_sync listener not registered - ensure kwil-db node/exts/evm-sync is imported") - } - - // Use mock listener if provided + // Prefer mock listener if provided, otherwise fallback to registered listener + var listenerFunc listeners.ListenFunc if mockFunc, hasMock := h.config.MockListeners["evm_sync"]; hasMock { listenerFunc = mockFunc + } else { + lf, exists := listeners.GetListener("evm_sync") + if !exists { + return fmt.Errorf("evm_sync listener not registered - ensure kwil-db node/exts/evm-sync is imported") + } + listenerFunc = lf } // Create context with timeout From a27d90597e023579c54e04be45ffbc5dc867d472 Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Tue, 9 Sep 2025 11:23:29 -0300 Subject: [PATCH 03/18] feat(migrations): enhance seed script handling and add test-only SQL files This commit introduces improvements to the migration system by embedding SQL files from a new `test_only` directory, allowing for better organization of test-specific seed scripts. The `GetSeedScriptPaths` function is updated to include paths from this directory, ensuring that all relevant SQL files are accessible during testing. Additionally, a new SQL file, `bootstrap_erc20.sql`, is added to the `test_only` directory, defining necessary tables for testing the ERC-20 bridge functionality. This enhancement aims to streamline the testing process and improve the overall structure of the migration files. --- internal/migrations/migration.go | 13 +- .../migrations/test_only/bootstrap_erc20.sql | 25 +++ .../erc20/erc20_bridge_simple_test.go | 170 +++++++++++++++++- tests/extensions/erc20/simple_mock.sql | 5 +- tests/streams/utils/erc20/helper.go | 4 + 5 files changed, 211 insertions(+), 6 deletions(-) create mode 100644 internal/migrations/test_only/bootstrap_erc20.sql diff --git a/internal/migrations/migration.go b/internal/migrations/migration.go index 117657cc4..3d4b80e7e 100644 --- a/internal/migrations/migration.go +++ b/internal/migrations/migration.go @@ -7,7 +7,7 @@ import ( "strings" ) -//go:embed *.sql +//go:embed *.sql test_only/*.sql var seedFiles embed.FS func GetSeedScriptPaths() []string { @@ -31,6 +31,17 @@ func GetSeedScriptPaths() []string { } } + // process test_only directory + entries, err = seedFiles.ReadDir("test_only") + if err != nil { + panic(err) + } + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".sql") { + seedsFiles = append(seedsFiles, filepath.Join(dir, "test_only", entry.Name())) + } + } + if len(seedsFiles) == 0 { panic("no seeds files found in embedded directory") } diff --git a/internal/migrations/test_only/bootstrap_erc20.sql b/internal/migrations/test_only/bootstrap_erc20.sql new file mode 100644 index 000000000..dddae2265 --- /dev/null +++ b/internal/migrations/test_only/bootstrap_erc20.sql @@ -0,0 +1,25 @@ +USE kwil_erc20_meta as kwil_erc20_meta; + +-- ordered-sync is provisioned by its genesis hook in node runtime. The test harness +-- does not run genesis, so we create the namespace and schema explicitly for tests. +CREATE NAMESPACE IF NOT EXISTS kwil_ordered_sync; +SET CURRENT NAMESPACE TO kwil_ordered_sync; + +CREATE TABLE IF NOT EXISTS topics ( + id UUID PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + resolve_func TEXT NOT NULL, + last_processed_point int8 +); + +CREATE TABLE IF NOT EXISTS pending_data ( + point int8, + topic_id UUID REFERENCES topics(id) ON UPDATE CASCADE ON DELETE CASCADE, + previous_point int8, + data bytea not null, + PRIMARY KEY (point, topic_id) +); + +CREATE TABLE IF NOT EXISTS meta( + version int8 PRIMARY KEY +); \ No newline at end of file diff --git a/tests/extensions/erc20/erc20_bridge_simple_test.go b/tests/extensions/erc20/erc20_bridge_simple_test.go index 1a34768b9..75c93b9e0 100644 --- a/tests/extensions/erc20/erc20_bridge_simple_test.go +++ b/tests/extensions/erc20/erc20_bridge_simple_test.go @@ -6,12 +6,17 @@ import ( "path/filepath" "runtime" "testing" + "time" "github.com/stretchr/testify/require" "github.com/trufnetwork/kwil-db/common" kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + + // Ensure ERC20 meta and ordered-sync extensions register their init/genesis hooks in test runtime + _ "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" ) // TestERC20BridgeSimpleBalance validates ERC-20 bridge extension initialization and balance() call. @@ -29,9 +34,12 @@ func TestERC20BridgeSimpleBalance(t *testing.T) { _, thisFile, _, _ := runtime.Caller(0) seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") + seedScripts := migrations.GetSeedScriptPaths() + seedScripts = append(seedScripts, seedPath) + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "erc20_bridge_simple_balance", - SeedScripts: []string{seedPath}, // This will fail if erc20_bridge extension doesn't exist + SeedScripts: seedScripts, FunctionTests: []kwilTesting.TestFunc{ func(ctx context.Context, platform *kwilTesting.Platform) error { // Arbitrary wallet address to query (no prior deposits => expected 0) @@ -107,3 +115,163 @@ func TestERC20BridgeMockListener(t *testing.T) { }, }, &testutils.Options{Options: testutils.GetTestOptions()}) } + +// TestERC20BridgeMockDepositEvent verifies that a mocked deposit event is broadcast by the listener. +func TestERC20BridgeMockDepositEvent(t *testing.T) { + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ + Name: "erc20_bridge_mock_deposit_event", + FunctionTests: []kwilTesting.TestFunc{ + func(ctx context.Context, platform *kwilTesting.Platform) error { + user := "0xabc0000000000000000000000000000000000001" + token := "0xdef0000000000000000000000000000000000002" + amount := "1000000000000000000" // 1 token + + // Create a mock deposit listener that emits an erc20_deposit event + mock := testerc20.MockERC20DepositListener(user, token, amount) + + // Configure ERC-20 bridge with mock listener + cfg := testerc20.NewERC20BridgeConfig(). + WithRPC("sepolia", "wss://mock-sepolia.com"). + WithSigner("test_bridge", "/dev/null"). + WithMockListener("evm_sync", mock). + WithAutoStart() + + // Setup test environment + helper, err := testerc20.SetupERC20BridgeTest(ctx, platform, cfg) + require.NoError(t, err) + defer helper.Cleanup() + + // Start listener and verify it works + require.NoError(t, helper.StartERC20Listener(ctx)) + + // Wait for the mocked deposit event to be broadcast + require.NoError(t, helper.WaitForListenerEvent("erc20_deposit", 2*time.Second)) + + // Stop listener + require.NoError(t, helper.StopERC20Listener()) + return nil + }, + }, + }, &testutils.Options{Options: testutils.GetTestOptions()}) +} + +// TestERC20BridgeMockDepositAffectsBalance simulates a deposit and checks balance via action. +// Note: This relies on the erc20 bridge extension wiring deposit events into balance(). +func TestERC20BridgeMockDepositAffectsBalance(t *testing.T) { + // Compute absolute path to the seed script relative to this test file + _, thisFile, _, _ := runtime.Caller(0) + seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") + + seedScripts := migrations.GetSeedScriptPaths() + seedScripts = append(seedScripts, seedPath) + + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ + Name: "erc20_bridge_mock_deposit_affects_balance", + SeedScripts: seedScripts, + FunctionTests: []kwilTesting.TestFunc{ + func(ctx context.Context, platform *kwilTesting.Platform) error { + user := "0xabc0000000000000000000000000000000000001" + token := "0xdef0000000000000000000000000000000000002" + amount := "1000000000000000000" // 1 token + + // Mock listener emits a deposit event + mock := testerc20.MockERC20DepositListener(user, token, amount) + + cfg := testerc20.NewERC20BridgeConfig(). + WithRPC("sepolia", "wss://mock-sepolia.com"). + WithSigner("test_bridge", "/dev/null"). + WithMockListener("evm_sync", mock). + WithAutoStart() + + helper, err := testerc20.SetupERC20BridgeTest(ctx, platform, cfg) + require.NoError(t, err) + defer helper.Cleanup() + + // Start listener and wait for event + require.NoError(t, helper.StartERC20Listener(ctx)) + require.NoError(t, helper.WaitForListenerEvent("erc20_deposit", 2*time.Second)) + + // Query balance via action seeded in simple_mock.sql + txCtx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: 1}, + Signer: platform.Deployer, + Caller: "0x0000000000000000000000000000000000000000", + TxID: platform.Txid(), + } + engCtx := &common.EngineContext{TxContext: txCtx} + + var got string + r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{user}, func(row *common.Row) error { + if len(row.Values) != 1 { + return fmt.Errorf("expected 1 column, got %d", len(row.Values)) + } + got = fmt.Sprintf("%v", row.Values[0]) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + // Expect non-zero balance (equal to mocked deposit) if extension applies events to balance + require.Equal(t, amount, got, "expected balance to reflect mocked deposit amount") + + // Stop listener + require.NoError(t, helper.StopERC20Listener()) + return nil + }, + }, + }, &testutils.Options{Options: testutils.GetTestOptions()}) +} + +// TestERC20BridgeAdminLockAffectsBalance uses the admin lock method to simulate a deposit and verifies balance. +func TestERC20BridgeAdminLockAffectsBalance(t *testing.T) { + // Compute absolute path to the seed script relative to this test file + _, thisFile, _, _ := runtime.Caller(0) + seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") + + seedScripts := migrations.GetSeedScriptPaths() + seedScripts = append(seedScripts, seedPath) + + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ + Name: "erc20_bridge_admin_lock_affects_balance", + SeedScripts: seedScripts, + FunctionTests: []kwilTesting.TestFunc{ + func(ctx context.Context, platform *kwilTesting.Platform) error { + user := "0xabc0000000000000000000000000000000000001" + amount := "1000000000000000000" + + txCtx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: 1}, + Signer: platform.Deployer, + Caller: "0x0000000000000000000000000000000000000000", + TxID: platform.Txid(), + } + engCtx := &common.EngineContext{TxContext: txCtx} + + // Attempt to call system method lock_admin to credit balance + _, err := platform.Engine.Call(engCtx, platform.DB, "sepolia_bridge", "lock_admin", []any{user, amount}, nil) + require.NoError(t, err) + + // Query balance via action seeded in simple_mock.sql + var got string + r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{user}, func(row *common.Row) error { + if len(row.Values) != 1 { + return fmt.Errorf("expected 1 column, got %d", len(row.Values)) + } + got = fmt.Sprintf("%v", row.Values[0]) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + require.Equal(t, amount, got, "expected balance to reflect admin lock amount") + return nil + }, + }, + }, &testutils.Options{Options: testutils.GetTestOptions()}) +} diff --git a/tests/extensions/erc20/simple_mock.sql b/tests/extensions/erc20/simple_mock.sql index 251c0d426..773359e8f 100644 --- a/tests/extensions/erc20/simple_mock.sql +++ b/tests/extensions/erc20/simple_mock.sql @@ -1,14 +1,11 @@ -- we'll use this to test configuration of the erc20 bridge in tests. -- if we successfully emit events and this listens, then we know the bridge is configured correctly. -USE kwil_ordered_sync as kwil_ordered_sync; -USE kwil_erc20_meta as kwil_erc20_meta; - USE erc20 { chain: 'sepolia', escrow: '0x1111111111111111111111111111111111111111' } AS sepolia_bridge; -CREATE ACTION get_balance($wallet_address TEXT) public { +CREATE ACTION get_balance($wallet_address TEXT) public RETURNS (balance DECIMAL(78, 0)) { $balance := sepolia_bridge.balance($wallet_address); return $balance; } \ No newline at end of file diff --git a/tests/streams/utils/erc20/helper.go b/tests/streams/utils/erc20/helper.go index caf64921b..91c3c21e1 100644 --- a/tests/streams/utils/erc20/helper.go +++ b/tests/streams/utils/erc20/helper.go @@ -96,6 +96,10 @@ func (h *ERC20BridgeTestHelper) StopERC20Listener() error { select { case err := <-h.listenerDone: h.running = false + // Treat graceful cancellations as successful shutdowns + if err == context.Canceled || err == context.DeadlineExceeded { + return nil + } return err case <-time.After(5 * time.Second): return fmt.Errorf("listener did not stop within timeout") From 2f20768ce75e28577a172248a52de0cd0426f4d0 Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Wed, 10 Sep 2025 08:39:18 -0300 Subject: [PATCH 04/18] feat(tests): add comprehensive ERC-20 bridge tests for epoch flow and injected transfers This commit introduces two new test files for the ERC-20 bridge functionality: - `erc20_bridge_epoch_test.go`: Implements `TestERC20BridgeEpochFlow`, validating the lock-and-issue, finalize, and confirm processes using shims. It ensures that rewards are correctly handled during epoch transitions. - `erc20_bridge_injection_test.go`: Implements `TestERC20BridgeInjectedTransferAffectsBalance`, which tests the impact of injected transfers on user balances, ensuring that the system accurately reflects these changes. Additionally, the existing `erc20_bridge_simple_test.go` file is updated to ensure proper instance synchronization and balance checks, enhancing the overall test coverage and reliability of the ERC-20 bridge functionality. --- .../erc20/erc20_bridge_epoch_test.go | 99 +++++++++++++++++++ .../erc20/erc20_bridge_injection_test.go | 76 ++++++++++++++ .../erc20/erc20_bridge_simple_test.go | 22 ++++- tests/streams/utils/erc20/inject.go | 96 ++++++++++++++++++ 4 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 tests/extensions/erc20/erc20_bridge_epoch_test.go create mode 100644 tests/extensions/erc20/erc20_bridge_injection_test.go create mode 100644 tests/streams/utils/erc20/inject.go diff --git a/tests/extensions/erc20/erc20_bridge_epoch_test.go b/tests/extensions/erc20/erc20_bridge_epoch_test.go new file mode 100644 index 000000000..742238d44 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_epoch_test.go @@ -0,0 +1,99 @@ +//go:build kwiltest + +package tests + +import ( + "context" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/node/internal/migrations" + testutils "github.com/trufnetwork/node/tests/streams/utils" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + + ethcommon "github.com/ethereum/go-ethereum/common" + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" + orderedsync "github.com/trufnetwork/kwil-db/node/exts/ordered-sync" +) + +// TestERC20BridgeEpochFlow validates lock-and-issue, finalize and confirm using shims. +func TestERC20BridgeEpochFlow(t *testing.T) { + _, thisFile, _, _ := runtime.Caller(0) + seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") + + seedScripts := migrations.GetSeedScriptPaths() + seedScripts = append(seedScripts, seedPath) + + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ + Name: "erc20_bridge_epoch_flow", + SeedScripts: seedScripts, + FunctionTests: []kwilTesting.TestFunc{ + func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + // ensure ordered-sync namespace and reset cache + require.NoError(t, orderedsync.ForTestingEnsureNamespace(ctx, app)) + orderedsync.ForTestingReset() + chain := "sepolia" + escrow := "0x1111111111111111111111111111111111111111" + erc20 := "0x2222222222222222222222222222222222222222" + user := "0xabc0000000000000000000000000000000000001" + value := "500000000000000000" // 0.5 + + // Ensure instance synced (register topic and create DB instance/epoch if missing) + _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) + require.NoError(t, err) + + // Make distribution period small for test determinism + require.NoError(t, erc20shim.ForTestingSetDistributionPeriod(ctx, app, chain, escrow, 1)) + + // Credit balance via injected transfer (simulates inbound deposit) + require.NoError(t, testerc20.InjectERC20Transfer(ctx, app, chain, escrow, erc20, user, escrow, value, 10, nil)) + + // Lock and issue directly into epoch (simulate bridge request) + require.NoError(t, erc20shim.ForTestingLockAndIssueDirect(ctx, app, chain, escrow, user, value)) + + // Assert pre-finalize: there should be a pending reward in epoch_rewards + preQ := ` + {kwil_erc20_meta}SELECT count(*) FROM epoch_rewards` + var preRows int + err = platform.Engine.ExecuteWithoutEngineCtx(ctx, platform.DB, preQ, nil, func(row *common.Row) error { + if len(row.Values) != 1 { + return nil + } + preRows = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err) + require.Greater(t, preRows, 0, "expected pending epoch reward before finalize") + + // Finalize current epoch and create next + var bh [32]byte + require.NoError(t, erc20shim.ForTestingFinalizeCurrentEpoch(ctx, app, chain, escrow, 11, bh)) + + // Confirm finalized epochs + require.NoError(t, erc20shim.ForTestingConfirmAllFinalizedEpochs(ctx, app, chain, escrow)) + + // Query confirmed rewards directly + q := ` + {kwil_erc20_meta}SELECT r.recipient, r.amount FROM epoch_rewards r + JOIN epochs e ON e.id = r.epoch_id + WHERE e.confirmed IS TRUE AND r.recipient = $recipient + ` + var rows int + ubytes := ethcommon.HexToAddress(user).Bytes() + err = platform.Engine.ExecuteWithoutEngineCtx(ctx, platform.DB, q, map[string]any{"recipient": ubytes}, func(row *common.Row) error { + rows++ + return nil + }) + require.NoError(t, err) + require.Greater(t, rows, 0, "expected confirmed wallet reward rows") + + return nil + }, + }, + }, &testutils.Options{Options: testutils.GetTestOptions()}) +} diff --git a/tests/extensions/erc20/erc20_bridge_injection_test.go b/tests/extensions/erc20/erc20_bridge_injection_test.go new file mode 100644 index 000000000..0a7895db5 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_injection_test.go @@ -0,0 +1,76 @@ +package tests + +import ( + "context" + "fmt" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/node/internal/migrations" + testutils "github.com/trufnetwork/node/tests/streams/utils" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" +) + +// TestERC20BridgeInjectedTransferAffectsBalance uses the production code path via ordered-sync/evm-sync shims. +func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { + // seed simple bridge action + _, thisFile, _, _ := runtime.Caller(0) + seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") + + seedScripts := migrations.GetSeedScriptPaths() + seedScripts = append(seedScripts, seedPath) + + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ + Name: "erc20_bridge_injected_transfer_affects_balance", + SeedScripts: seedScripts, + FunctionTests: []kwilTesting.TestFunc{ + func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + // ensure ordered-sync topic and meta schema via helpers + chain := "sepolia" + escrow := "0x1111111111111111111111111111111111111111" + erc20 := "0x2222222222222222222222222222222222222222" + user := "0xabc0000000000000000000000000000000000001" + value := "1000000000000000000" + + _, _ = erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) + + // Inject a transfer: from user to escrow (lock/credit path) + err := testerc20.InjectERC20Transfer(ctx, app, chain, escrow, erc20, user, escrow, value, 1, nil) + require.NoError(t, err) + + // Query balance via action seeded in simple_mock.sql + txCtx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: 2}, + Signer: platform.Deployer, + Caller: "0x0000000000000000000000000000000000000000", + TxID: platform.Txid(), + } + engCtx := &common.EngineContext{TxContext: txCtx} + + var got string + r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{user}, func(row *common.Row) error { + if len(row.Values) != 1 { + return fmt.Errorf("expected 1 column, got %d", len(row.Values)) + } + got = fmt.Sprintf("%v", row.Values[0]) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + require.Equal(t, value, got, "expected balance to reflect injected transfer amount") + return nil + }, + }, + }, &testutils.Options{Options: testutils.GetTestOptions()}) +} diff --git a/tests/extensions/erc20/erc20_bridge_simple_test.go b/tests/extensions/erc20/erc20_bridge_simple_test.go index 75c93b9e0..851fa6a03 100644 --- a/tests/extensions/erc20/erc20_bridge_simple_test.go +++ b/tests/extensions/erc20/erc20_bridge_simple_test.go @@ -1,8 +1,11 @@ +//go:build !kwiltest + package tests import ( "context" "fmt" + "math/big" "path/filepath" "runtime" "testing" @@ -10,13 +13,12 @@ import ( "github.com/stretchr/testify/require" "github.com/trufnetwork/kwil-db/common" + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/kwil-db/types" "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" - - // Ensure ERC20 meta and ordered-sync extensions register their init/genesis hooks in test runtime - _ "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" ) // TestERC20BridgeSimpleBalance validates ERC-20 bridge extension initialization and balance() call. @@ -42,6 +44,13 @@ func TestERC20BridgeSimpleBalance(t *testing.T) { SeedScripts: seedScripts, FunctionTests: []kwilTesting.TestFunc{ func(ctx context.Context, platform *kwilTesting.Platform) error { + // Ensure instance via helper to create meta schema and instance idempotently + app := &common.App{DB: platform.DB, Engine: platform.Engine} + chain := "sepolia" + escrow := "0x1111111111111111111111111111111111111111" + erc20 := "0x2222222222222222222222222222222222222222" + _, _ = erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) + // Arbitrary wallet address to query (no prior deposits => expected 0) wallet := "0x1111111111111111111111111111111111110001" @@ -252,7 +261,12 @@ func TestERC20BridgeAdminLockAffectsBalance(t *testing.T) { engCtx := &common.EngineContext{TxContext: txCtx} // Attempt to call system method lock_admin to credit balance - _, err := platform.Engine.Call(engCtx, platform.DB, "sepolia_bridge", "lock_admin", []any{user, amount}, nil) + // Convert amount string to Decimal (numeric(78,0)) + amtDec, err := types.NewDecimalFromBigInt(new(big.Int).SetString(amount, 10)) + if err != nil { + return fmt.Errorf("decimal parse: %w", err) + } + _, err = platform.Engine.Call(engCtx, platform.DB, "sepolia_bridge", "lock_admin", []any{user, amtDec}, nil) require.NoError(t, err) // Query balance via action seeded in simple_mock.sql diff --git a/tests/streams/utils/erc20/inject.go b/tests/streams/utils/erc20/inject.go new file mode 100644 index 000000000..d8c9a9963 --- /dev/null +++ b/tests/streams/utils/erc20/inject.go @@ -0,0 +1,96 @@ +//go:build kwiltest + +package erc20 + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "math/big" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/trufnetwork/kwil-db/common" + "github.com/trufnetwork/kwil-db/core/types" + erc20bridge "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" + evmsync "github.com/trufnetwork/kwil-db/node/exts/evm-sync" + orderedsync "github.com/trufnetwork/kwil-db/node/exts/ordered-sync" +) + +// InjectERC20Transfer forces an instance synced and injects a synthetic Transfer log that credits balance. +func InjectERC20Transfer(ctx context.Context, app *common.App, chain, escrow, erc20Addr, fromHex, toHex string, valueStr string, point int64, prev *int64) error { + // 1) Ensure instance exists and is synced + id, err := erc20bridge.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20Addr, 18) + if err != nil { + return fmt.Errorf("force sync instance: %w", err) + } + + // 2) Compute ordered-sync topic + topic := erc20bridge.ForTestingTransferListenerTopic(*id) + + // 3) Build a synthetic transfer log + from := ethcommon.HexToAddress(fromHex) + to := ethcommon.HexToAddress(toHex) + erc20Address := ethcommon.HexToAddress(erc20Addr) + var bn big.Int + if _, ok := bn.SetString(valueStr, 10); !ok { + return fmt.Errorf("invalid value: %s", valueStr) + } + // topics: signature + from + to + topics := []ethcommon.Hash{ + ethcommon.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), + ethcommon.BytesToHash(from.Bytes()), + ethcommon.BytesToHash(to.Bytes()), + } + // data: 32-byte big-endian value + val32 := types.BigIntToHash32(&bn) + lg := ðtypes.Log{ + Address: erc20Address, + Topics: topics, + Data: val32[:], + BlockNumber: uint64(point), + TxHash: ethcommon.Hash{}, + TxIndex: 0, + BlockHash: ethcommon.Hash{}, + Index: 0, + Removed: false, + } + ethLog := &evmsync.EthLog{Metadata: []byte("e20trsnfr"), Log: lg} + + // 4) Serialize like production and store via ordered-sync + logsData, err := serializeEthLogsLocal([]*evmsync.EthLog{ethLog}) + if err != nil { + return fmt.Errorf("serialize logs: %w", err) + } + + if err := orderedsync.ForTestingStoreLogs(ctx, app, topic, logsData, point, prev); err != nil { + return fmt.Errorf("store logs: %w", err) + } + + // 5) Resolve via end-block path + if err := orderedsync.ForTestingResolve(ctx, app, &common.BlockContext{Height: point, Timestamp: point}); err != nil { + return fmt.Errorf("resolve: %w", err) + } + + return nil +} + +// serializeEthLogsLocal mirrors evmsync.serializeEthLogs for test tag builds +func serializeEthLogsLocal(logs []*evmsync.EthLog) ([]byte, error) { + buf := new(bytes.Buffer) + for _, l := range logs { + b, err := l.MarshalBinary() + if err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, uint64(len(b))); err != nil { + return nil, err + } + if _, err := buf.Write(b); err != nil { + return nil, err + } + } + return buf.Bytes(), nil +} From 788a71f525a5ff956ac38c704a5df0f4ba644ad1 Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Wed, 10 Sep 2025 12:24:14 -0300 Subject: [PATCH 05/18] refactor(tests): streamline ERC-20 bridge tests and remove mock listener dependencies This commit refactors the ERC-20 bridge test suite by removing the mock listener and simplifying the test setup. Key changes include: - Updated `erc20_bridge_simple_test.go` to utilize transaction-based isolation for better test reliability and consistency. - Introduced `WithERC20TestSetup` and `WithERC20TestSetupTx` helpers to streamline the setup process for ERC-20 bridge tests. - Removed complex listener tests and focused on essential functionality, ensuring that tests are easier to maintain and understand. - Enhanced existing tests to ensure proper instance synchronization and balance checks, improving overall test coverage. These changes aim to enhance the clarity and maintainability of the ERC-20 bridge testing framework. --- .../erc20/erc20_bridge_epoch_test.go | 7 +- .../erc20/erc20_bridge_injection_test.go | 2 +- .../erc20/erc20_bridge_simple_test.go | 332 ++++++------------ .../tn_cache/cache_height_tracking_test.go | 17 +- .../tn_cache/cache_integration_test.go | 16 +- .../tn_cache/cache_observability_test.go | 8 +- .../tn_cache/resolution_transaction_test.go | 9 +- tests/streams/utils/erc20/helper.go | 267 +++++++------- tests/streams/utils/erc20/inject.go | 2 - tests/streams/utils/runner.go | 61 +--- tests/streams/utils/utils.go | 57 +-- 11 files changed, 285 insertions(+), 493 deletions(-) diff --git a/tests/extensions/erc20/erc20_bridge_epoch_test.go b/tests/extensions/erc20/erc20_bridge_epoch_test.go index 742238d44..bc307dddc 100644 --- a/tests/extensions/erc20/erc20_bridge_epoch_test.go +++ b/tests/extensions/erc20/erc20_bridge_epoch_test.go @@ -34,11 +34,12 @@ func TestERC20BridgeEpochFlow(t *testing.T) { FunctionTests: []kwilTesting.TestFunc{ func(ctx context.Context, platform *kwilTesting.Platform) error { app := &common.App{DB: platform.DB, Engine: platform.Engine} - // ensure ordered-sync namespace and reset cache + // ensure ordered-sync namespace require.NoError(t, orderedsync.ForTestingEnsureNamespace(ctx, app)) - orderedsync.ForTestingReset() + + // Note: Global state reset should be handled by test framework, not individual tests chain := "sepolia" - escrow := "0x1111111111111111111111111111111111111111" + escrow := "0xdddddddddddddddddddddddddddddddddddddddd" erc20 := "0x2222222222222222222222222222222222222222" user := "0xabc0000000000000000000000000000000000001" value := "500000000000000000" // 0.5 diff --git a/tests/extensions/erc20/erc20_bridge_injection_test.go b/tests/extensions/erc20/erc20_bridge_injection_test.go index 0a7895db5..e6a69cbcd 100644 --- a/tests/extensions/erc20/erc20_bridge_injection_test.go +++ b/tests/extensions/erc20/erc20_bridge_injection_test.go @@ -34,7 +34,7 @@ func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { app := &common.App{DB: platform.DB, Engine: platform.Engine} // ensure ordered-sync topic and meta schema via helpers chain := "sepolia" - escrow := "0x1111111111111111111111111111111111111111" + escrow := "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" erc20 := "0x2222222222222222222222222222222222222222" user := "0xabc0000000000000000000000000000000000001" value := "1000000000000000000" diff --git a/tests/extensions/erc20/erc20_bridge_simple_test.go b/tests/extensions/erc20/erc20_bridge_simple_test.go index 851fa6a03..c64b6e076 100644 --- a/tests/extensions/erc20/erc20_bridge_simple_test.go +++ b/tests/extensions/erc20/erc20_bridge_simple_test.go @@ -1,37 +1,39 @@ -//go:build !kwiltest - package tests import ( "context" "fmt" - "math/big" "path/filepath" "runtime" "testing" - "time" "github.com/stretchr/testify/require" - "github.com/trufnetwork/kwil-db/common" - erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" kwilTesting "github.com/trufnetwork/kwil-db/testing" - "github.com/trufnetwork/kwil-db/types" "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" ) -// TestERC20BridgeSimpleBalance validates ERC-20 bridge extension initialization and balance() call. -// This test requires the erc20_bridge extension to be available in the test environment. -// -// BLOCKER: The erc20_bridge extension is not currently available in the test environment. -// When this extension becomes available, this test will: -// 1. Execute the USE erc20_bridge statement from simple_mock.sql -// 2. Call the get_balance action which proxies to sepolia_bridge.balance() -// 3. Verify it returns "0" for a wallet with no deposit events -// -// Until then, this test will fail with a clear error about the missing extension. -func TestERC20BridgeSimpleBalance(t *testing.T) { +// WithERC20TestSetup is a helper function that sets up the test environment with ERC-20 bridge +// Follows the same pattern as WithQueryTestSetup in query_test.go for consistency +func WithERC20TestSetup(escrowAddr string) func(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error { + setupFunc := testerc20.WithERC20TestSetup(escrowAddr) + return func(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // First run the ERC20 setup + err := setupFunc(ctx, platform) + if err != nil { + return err + } + // Then run the test function + return testFn(ctx, platform) + } + } +} + +// TestERC20BridgeSimpleBalanceTx uses transaction-based isolation for perfect test isolation +// This allows reusing the same user accounts and escrow addresses across tests. +func TestERC20BridgeSimpleBalanceTx(t *testing.T) { // Compute absolute path to the seed script relative to this test file _, thisFile, _, _ := runtime.Caller(0) seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") @@ -40,133 +42,74 @@ func TestERC20BridgeSimpleBalance(t *testing.T) { seedScripts = append(seedScripts, seedPath) testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: "erc20_bridge_simple_balance", + Name: "erc20_bridge_simple_balance_tx", SeedScripts: seedScripts, FunctionTests: []kwilTesting.TestFunc{ func(ctx context.Context, platform *kwilTesting.Platform) error { - // Ensure instance via helper to create meta schema and instance idempotently - app := &common.App{DB: platform.DB, Engine: platform.Engine} - chain := "sepolia" - escrow := "0x1111111111111111111111111111111111111111" - erc20 := "0x2222222222222222222222222222222222222222" - _, _ = erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) - - // Arbitrary wallet address to query (no prior deposits => expected 0) - wallet := "0x1111111111111111111111111111111111110001" + // Use transaction-based isolation with WithTx + testFunc := testutils.WithTx(platform, func(t *testing.T, txPlatform *kwilTesting.Platform) { + // Setup ERC20 with the default escrow from simple_mock.sql + testerc20.WithERC20TestSetupTx("0x1111111111111111111111111111111111111111")(t, txPlatform) - txCtx := &common.TxContext{ - Ctx: ctx, - BlockContext: &common.BlockContext{Height: 1}, - Signer: platform.Deployer, - Caller: "0x0000000000000000000000000000000000000000", - TxID: platform.Txid(), - } - engCtx := &common.EngineContext{TxContext: txCtx} - - // Call our seeded action which proxies to sepolia_bridge.balance($wallet) - var got string - r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{wallet}, func(row *common.Row) error { - if len(row.Values) != 1 { - return fmt.Errorf("expected 1 column, got %d", len(row.Values)) - } - got = fmt.Sprintf("%v", row.Values[0]) - return nil + // Now run the actual test logic with the transaction-scoped platform + testSimpleBalanceTx(t, txPlatform) }) - require.NoError(t, err) - if r != nil && r.Error != nil { - return r.Error - } - // Expect 0 for no prior deposits - require.Equal(t, "0", got, "expected zero balance for fresh wallet without deposit events") + // Execute the transaction-wrapped test + testFunc(t) return nil }, }, }, &testutils.Options{Options: testutils.GetTestOptions()}) } -// TestERC20BridgeMockListener tests our ERC-20 bridge testing helpers with a mock listener. -// This doesn't require the real erc20_bridge extension but validates our test infrastructure. -func TestERC20BridgeMockListener(t *testing.T) { - testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: "erc20_bridge_mock_listener", - FunctionTests: []kwilTesting.TestFunc{ - func(ctx context.Context, platform *kwilTesting.Platform) error { - // Create mock listener that broadcasts ERC-20 transfer events - mock := testutils.MockERC20Listener([]string{"erc20_transfer"}) - - // Configure ERC-20 bridge with mock listener - cfg := testutils.NewERC20BridgeConfig(). - WithRPC("sepolia", "wss://mock-sepolia.com"). - WithSigner("test_bridge", "/dev/null"). - WithMockListener("evm_sync", mock). - WithAutoStart() - - // Setup test environment - helper, err := testerc20.SetupERC20BridgeTest(ctx, platform, cfg) - require.NoError(t, err) - defer helper.Cleanup() +// testSimpleBalanceTx performs the actual balance test logic with transaction-scoped platform +func testSimpleBalanceTx(t *testing.T, txPlatform *kwilTesting.Platform) { + // Test-specific constants - can reuse the same user across tests now! + const testWallet = "0x1111111111111111111111111111111111110001" - // Start listener and verify it works - require.NoError(t, helper.StartERC20Listener(ctx)) + // Query balance for a wallet with no prior deposits + balance, err := testerc20.GetUserBalance(context.Background(), txPlatform, testWallet) + require.NoError(t, err) - // Verify listener is running - require.True(t, helper.IsListenerRunning(), "listener should be running") - - // Stop listener - require.NoError(t, helper.StopERC20Listener()) - - // Verify listener stopped - require.False(t, helper.IsListenerRunning(), "listener should have stopped") - - return nil - }, - }, - }, &testutils.Options{Options: testutils.GetTestOptions()}) + // Should return "0" for wallet with no prior deposits + require.Equal(t, "0", balance, "expected balance of 0 for wallet with no prior deposits") } -// TestERC20BridgeMockDepositEvent verifies that a mocked deposit event is broadcast by the listener. -func TestERC20BridgeMockDepositEvent(t *testing.T) { - testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: "erc20_bridge_mock_deposit_event", - FunctionTests: []kwilTesting.TestFunc{ - func(ctx context.Context, platform *kwilTesting.Platform) error { - user := "0xabc0000000000000000000000000000000000001" - token := "0xdef0000000000000000000000000000000000002" - amount := "1000000000000000000" // 1 token - - // Create a mock deposit listener that emits an erc20_deposit event - mock := testerc20.MockERC20DepositListener(user, token, amount) - - // Configure ERC-20 bridge with mock listener - cfg := testerc20.NewERC20BridgeConfig(). - WithRPC("sepolia", "wss://mock-sepolia.com"). - WithSigner("test_bridge", "/dev/null"). - WithMockListener("evm_sync", mock). - WithAutoStart() - - // Setup test environment - helper, err := testerc20.SetupERC20BridgeTest(ctx, platform, cfg) - require.NoError(t, err) - defer helper.Cleanup() - - // Start listener and verify it works - require.NoError(t, helper.StartERC20Listener(ctx)) - - // Wait for the mocked deposit event to be broadcast - require.NoError(t, helper.WaitForListenerEvent("erc20_deposit", 2*time.Second)) - - // Stop listener - require.NoError(t, helper.StopERC20Listener()) - return nil - }, - }, - }, &testutils.Options{Options: testutils.GetTestOptions()}) +// testSimpleBalance performs the actual balance test logic (legacy version) +func testSimpleBalance(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // Test-specific constants + const testWallet = "0x1111111111111111111111111111111111110001" + + // Query balance for a wallet with no prior deposits + balance, err := testerc20.GetUserBalance(ctx, platform, testWallet) + if err != nil { + return fmt.Errorf("failed to get user balance: %w", err) + } + + // Should return "0" for wallet with no prior deposits + require.Equal(t, "0", balance, "expected balance of 0 for wallet with no prior deposits") + return nil + } } -// TestERC20BridgeMockDepositAffectsBalance simulates a deposit and checks balance via action. -// Note: This relies on the erc20 bridge extension wiring deposit events into balance(). -func TestERC20BridgeMockDepositAffectsBalance(t *testing.T) { +// Removed complex listener tests - keeping only simple extension functionality tests +// that follow the same pattern as query_test.go for consistency + +// TestERC20BridgeAdminLockAffectsBalanceTx validates that the lock_admin system method +// correctly reduces a user's balance when called with OverrideAuthz. +// Uses transaction-based isolation for proper test cleanup. +// +// Test Scenario: +// 1. Set up ERC-20 bridge extension with a synced instance +// 2. Credit user's balance with a realistic ERC-20 transfer (simulating deposit) +// 3. Call lock_admin to lock the user's tokens +// 4. Verify user's balance is reduced to 0 +// +// This test ensures admin lock functionality works correctly and integrates +// with the extension's balance tracking. +func TestERC20BridgeAdminLockAffectsBalanceTx(t *testing.T) { // Compute absolute path to the seed script relative to this test file _, thisFile, _, _ := runtime.Caller(0) seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") @@ -175,117 +118,50 @@ func TestERC20BridgeMockDepositAffectsBalance(t *testing.T) { seedScripts = append(seedScripts, seedPath) testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: "erc20_bridge_mock_deposit_affects_balance", + Name: "erc20_bridge_admin_lock_affects_balance_tx", SeedScripts: seedScripts, FunctionTests: []kwilTesting.TestFunc{ func(ctx context.Context, platform *kwilTesting.Platform) error { - user := "0xabc0000000000000000000000000000000000001" - token := "0xdef0000000000000000000000000000000000002" - amount := "1000000000000000000" // 1 token - - // Mock listener emits a deposit event - mock := testerc20.MockERC20DepositListener(user, token, amount) - - cfg := testerc20.NewERC20BridgeConfig(). - WithRPC("sepolia", "wss://mock-sepolia.com"). - WithSigner("test_bridge", "/dev/null"). - WithMockListener("evm_sync", mock). - WithAutoStart() - - helper, err := testerc20.SetupERC20BridgeTest(ctx, platform, cfg) - require.NoError(t, err) - defer helper.Cleanup() - - // Start listener and wait for event - require.NoError(t, helper.StartERC20Listener(ctx)) - require.NoError(t, helper.WaitForListenerEvent("erc20_deposit", 2*time.Second)) + // Use transaction-based isolation with WithTx + testFunc := testutils.WithTx(platform, func(t *testing.T, txPlatform *kwilTesting.Platform) { + // Setup ERC20 with the default escrow from simple_mock.sql + testerc20.WithERC20TestSetupTx("0x1111111111111111111111111111111111111111")(t, txPlatform) - // Query balance via action seeded in simple_mock.sql - txCtx := &common.TxContext{ - Ctx: ctx, - BlockContext: &common.BlockContext{Height: 1}, - Signer: platform.Deployer, - Caller: "0x0000000000000000000000000000000000000000", - TxID: platform.Txid(), - } - engCtx := &common.EngineContext{TxContext: txCtx} - - var got string - r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{user}, func(row *common.Row) error { - if len(row.Values) != 1 { - return fmt.Errorf("expected 1 column, got %d", len(row.Values)) - } - got = fmt.Sprintf("%v", row.Values[0]) - return nil + // Now run the actual test logic with the transaction-scoped platform + testAdminLockTx(t, txPlatform) }) - require.NoError(t, err) - if r != nil && r.Error != nil { - return r.Error - } - - // Expect non-zero balance (equal to mocked deposit) if extension applies events to balance - require.Equal(t, amount, got, "expected balance to reflect mocked deposit amount") - // Stop listener - require.NoError(t, helper.StopERC20Listener()) + // Execute the transaction-wrapped test + testFunc(t) return nil }, }, }, &testutils.Options{Options: testutils.GetTestOptions()}) } -// TestERC20BridgeAdminLockAffectsBalance uses the admin lock method to simulate a deposit and verifies balance. -func TestERC20BridgeAdminLockAffectsBalance(t *testing.T) { - // Compute absolute path to the seed script relative to this test file - _, thisFile, _, _ := runtime.Caller(0) - seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") - - seedScripts := migrations.GetSeedScriptPaths() - seedScripts = append(seedScripts, seedPath) - - testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: "erc20_bridge_admin_lock_affects_balance", - SeedScripts: seedScripts, - FunctionTests: []kwilTesting.TestFunc{ - func(ctx context.Context, platform *kwilTesting.Platform) error { - user := "0xabc0000000000000000000000000000000000001" - amount := "1000000000000000000" - - txCtx := &common.TxContext{ - Ctx: ctx, - BlockContext: &common.BlockContext{Height: 1}, - Signer: platform.Deployer, - Caller: "0x0000000000000000000000000000000000000000", - TxID: platform.Txid(), - } - engCtx := &common.EngineContext{TxContext: txCtx} - - // Attempt to call system method lock_admin to credit balance - // Convert amount string to Decimal (numeric(78,0)) - amtDec, err := types.NewDecimalFromBigInt(new(big.Int).SetString(amount, 10)) - if err != nil { - return fmt.Errorf("decimal parse: %w", err) - } - _, err = platform.Engine.Call(engCtx, platform.DB, "sepolia_bridge", "lock_admin", []any{user, amtDec}, nil) - require.NoError(t, err) - - // Query balance via action seeded in simple_mock.sql - var got string - r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{user}, func(row *common.Row) error { - if len(row.Values) != 1 { - return fmt.Errorf("expected 1 column, got %d", len(row.Values)) - } - got = fmt.Sprintf("%v", row.Values[0]) - return nil - }) - require.NoError(t, err) - if r != nil && r.Error != nil { - return r.Error - } - - require.Equal(t, amount, got, "expected balance to reflect admin lock amount") - return nil - }, - }, - }, &testutils.Options{Options: testutils.GetTestOptions()}) +// testAdminLockTx performs the actual admin lock test logic with transaction-scoped platform +func testAdminLockTx(t *testing.T, txPlatform *kwilTesting.Platform) { + // Test-specific constants - can reuse the same user across tests now! + const ( + testUser = "0xabc0000000000000000000000000000000000001" + testAmount = "1000000000000000000" // 1.0 tokens + ) + + // Step 1: Pre-credit user's balance with realistic ERC-20 transfer + // This simulates a user depositing tokens into the bridge escrow + // Use the same escrow address as the test setup + err := testerc20.CreditUserBalance(context.Background(), txPlatform, "0x1111111111111111111111111111111111111111", testUser, testAmount) + require.NoError(t, err) + + // Step 2: Execute admin lock operation + // This should reduce user's balance by the locked amount + err = testerc20.CallLockAdmin(context.Background(), txPlatform, testUser, testAmount) + require.NoError(t, err) + + // Step 3: Verify balance was correctly reduced + // After locking all tokens, balance should be 0 + finalBalance, err := testerc20.GetUserBalance(context.Background(), txPlatform, testUser) + require.NoError(t, err) + require.Equal(t, "0", finalBalance, + "expected user balance to be zero after admin lock of entire balance") } diff --git a/tests/extensions/tn_cache/cache_height_tracking_test.go b/tests/extensions/tn_cache/cache_height_tracking_test.go index f0c4be472..39fd59fee 100644 --- a/tests/extensions/tn_cache/cache_height_tracking_test.go +++ b/tests/extensions/tn_cache/cache_height_tracking_test.go @@ -11,8 +11,10 @@ import ( "github.com/stretchr/testify/require" "github.com/trufnetwork/kwil-db/node/meta" kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/node/extensions/tn_cache" "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" + "github.com/trufnetwork/node/tests/streams/utils/cache" "github.com/trufnetwork/node/tests/streams/utils/procedure" "github.com/trufnetwork/node/tests/streams/utils/setup" "github.com/trufnetwork/sdk-go/core/types" @@ -26,14 +28,14 @@ import ( func TestCacheHeightTracking(t *testing.T) { deployer := "0x0000000000000000000000000000000000000456" streamId := util.GenerateStreamId("height_tracking_test") - cacheConfig := testutils.TestCache(deployer, streamId.String()) + cacheConfig := testutils.SimpleCache(deployer, streamId.String()) testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "cache_height_tracking_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ func(ctx context.Context, platform *kwilTesting.Platform) error { - helper := testutils.SetupCacheTest(ctx, platform, cacheConfig) + helper := cache.SetupCacheTest(ctx, platform, cacheConfig) defer helper.Cleanup() deployerAddr, err := util.NewEthereumAddressFromString(deployer) @@ -60,7 +62,7 @@ func TestCacheHeightTracking(t *testing.T) { require.NoError(t, err) // Refresh cache to populate data with height - recordsCached, err := helper.RefreshAllStreamsSync(ctx) + recordsCached, err := tn_cache.GetTestHelper().RefreshAllStreamsSync(ctx) require.NoError(t, err) assert.Greater(t, recordsCached, 0, "Should have cached some records") @@ -75,7 +77,7 @@ func TestCacheHeightTracking(t *testing.T) { useCache := true fromTime := int64(50) toTime := int64(250) - + // Execute query that will use cache and return logs result, err := procedure.GetRecordWithLogs(ctx, procedure.GetRecordInput{ Platform: platform, @@ -99,15 +101,15 @@ func TestCacheHeightTracking(t *testing.T) { break } } - + require.NotEmpty(t, cacheLog, "Should have cache hit log with height information") t.Logf("Cache log: %s", cacheLog) - + // Verify the log contains height fields assert.Contains(t, cacheLog, "cache_hit", "Log should contain cache_hit field") assert.Contains(t, cacheLog, "cache_height", "Log should contain cache_height") assert.Contains(t, cacheLog, "cache_refreshed_at_timestamp", "Log should contain cache_refreshed_at_timestamp") - + // The cache_height should be 1 (when we cached) assert.Contains(t, cacheLog, `"cache_hit": true`, "Should be a cache hit") assert.Contains(t, cacheLog, `"cache_height": 1`, "Cache height should be 1") @@ -119,4 +121,3 @@ func TestCacheHeightTracking(t *testing.T) { }, }, testutils.GetTestOptionsWithCache(cacheConfig)) } - diff --git a/tests/extensions/tn_cache/cache_integration_test.go b/tests/extensions/tn_cache/cache_integration_test.go index e4bca2302..9ee34a70b 100644 --- a/tests/extensions/tn_cache/cache_integration_test.go +++ b/tests/extensions/tn_cache/cache_integration_test.go @@ -11,8 +11,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/node/extensions/tn_cache" "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" + "github.com/trufnetwork/node/tests/streams/utils/cache" "github.com/trufnetwork/node/tests/streams/utils/procedure" "github.com/trufnetwork/node/tests/streams/utils/setup" "github.com/trufnetwork/sdk-go/core/types" @@ -29,7 +31,7 @@ var composedStreamId = util.GenerateStreamId("cache_test_composed") func TestCacheIntegration(t *testing.T) { // Create cache configuration for testing // This sets up a cache that won't auto-refresh (scheduled for Feb 31st) - cacheConfig := testutils.TestCache("0x0000000000000000000000000000000000000123", composedStreamId.String()) + cacheConfig := testutils.SimpleCache("0x0000000000000000000000000000000000000123", composedStreamId.String()) // Run the test with cache enabled using the new wrapper testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ @@ -44,7 +46,7 @@ func TestCacheIntegration(t *testing.T) { func testCacheBasicFunctionality(t *testing.T, cacheConfig *testutils.CacheOptions) func(ctx context.Context, platform *kwilTesting.Platform) error { return func(ctx context.Context, platform *kwilTesting.Platform) error { // Cache is already set up by the wrapper, but we need the helper for RefreshCache - helper := testutils.SetupCacheTest(ctx, platform, cacheConfig) + helper := cache.SetupCacheTest(ctx, platform, cacheConfig) defer helper.Cleanup() // Create a composed stream with child streams @@ -117,7 +119,7 @@ func testCacheBasicFunctionality(t *testing.T, cacheConfig *testutils.CacheOptio // No need to commit since we're using the same DB connection now // Refresh the cache to populate it with our test data - recordsCached, err := helper.RefreshAllStreamsSync(ctx) + recordsCached, err := tn_cache.GetTestHelper().RefreshAllStreamsSync(ctx) require.NoError(t, err, "should be able to refresh cache") assert.Equal(t, 3, recordsCached, "should cache 3 records") @@ -242,7 +244,7 @@ func TestCacheIncludeChildrenForNestedComposed(t *testing.T) { func testCacheIncludeChildren(t *testing.T, cacheConfig *testutils.CacheOptions) func(ctx context.Context, platform *kwilTesting.Platform) error { return func(ctx context.Context, platform *kwilTesting.Platform) error { - helper := testutils.SetupCacheTest(ctx, platform, cacheConfig) + helper := cache.SetupCacheTest(ctx, platform, cacheConfig) defer helper.Cleanup() deployer, err := util.NewEthereumAddressFromString("0x0000000000000000000000000000000000000123") @@ -317,7 +319,7 @@ func testCacheIncludeChildren(t *testing.T, cacheConfig *testutils.CacheOptions) verifyCacheStreamExists(t, ctx, platform, deployer.Address(), parentComposedId.String()) // Refresh cache - should cache parent and auto-resolve children - recordsCached, err := helper.RefreshAllStreamsSync(ctx) + recordsCached, err := tn_cache.GetTestHelper().RefreshAllStreamsSync(ctx) require.NoError(t, err) assert.Equal(t, 9, recordsCached, "should cache 9 records (3 parent + 3 child1 + 3 child2)") @@ -325,10 +327,10 @@ func testCacheIncludeChildren(t *testing.T, cacheConfig *testutils.CacheOptions) verifyChildStreamsInCache(t, ctx, platform, deployer.Address(), childComposed1Id.String(), childComposed2Id.String()) // Refresh cache for child streams to populate their data - _, err = helper.RefreshAllStreamsSync(ctx) + _, err = tn_cache.GetTestHelper().RefreshAllStreamsSync(ctx) require.NoError(t, err, "Failed to refresh child composed 1 cache") - _, err = helper.RefreshAllStreamsSync(ctx) + _, err = tn_cache.GetTestHelper().RefreshAllStreamsSync(ctx) require.NoError(t, err, "Failed to refresh child composed 2 cache") // Verify all composed streams have cached data (primitives should not) diff --git a/tests/extensions/tn_cache/cache_observability_test.go b/tests/extensions/tn_cache/cache_observability_test.go index daf31fd48..b506a3cab 100644 --- a/tests/extensions/tn_cache/cache_observability_test.go +++ b/tests/extensions/tn_cache/cache_observability_test.go @@ -10,8 +10,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/node/extensions/tn_cache" "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" + "github.com/trufnetwork/node/tests/streams/utils/cache" "github.com/trufnetwork/node/tests/streams/utils/procedure" "github.com/trufnetwork/node/tests/streams/utils/setup" "github.com/trufnetwork/sdk-go/core/types" @@ -25,7 +27,7 @@ import ( func TestCacheObservability(t *testing.T) { deployer := "0x0000000000000000000000000000000000000123" streamId := util.GenerateStreamId("cache_observability_test") - cacheConfig := testutils.TestCache(deployer, streamId.String()) + cacheConfig := testutils.SimpleCache(deployer, streamId.String()) testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "cache_observability_test", @@ -33,7 +35,7 @@ func TestCacheObservability(t *testing.T) { FunctionTests: []kwilTesting.TestFunc{ func(ctx context.Context, platform *kwilTesting.Platform) error { // Cache is already set up by the wrapper, but we need the helper for RefreshCache - helper := testutils.SetupCacheTest(ctx, platform, cacheConfig) + helper := cache.SetupCacheTest(ctx, platform, cacheConfig) defer helper.Cleanup() deployerAddr, err := util.NewEthereumAddressFromString(deployer) @@ -98,7 +100,7 @@ func TestCacheObservability(t *testing.T) { assert.Equal(t, false, missLog["cache_hit"], "First query should be cache miss") // Refresh cache for hit test - recordsCached, err := helper.RefreshAllStreamsSync(ctx) + recordsCached, err := tn_cache.GetTestHelper().RefreshAllStreamsSync(ctx) require.NoError(t, err) assert.Equal(t, 2, recordsCached, "Should cache 2 aggregated records") diff --git a/tests/extensions/tn_cache/resolution_transaction_test.go b/tests/extensions/tn_cache/resolution_transaction_test.go index 81bd38d24..1f0719695 100644 --- a/tests/extensions/tn_cache/resolution_transaction_test.go +++ b/tests/extensions/tn_cache/resolution_transaction_test.go @@ -11,6 +11,7 @@ import ( "github.com/trufnetwork/node/extensions/tn_cache" "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" + "github.com/trufnetwork/node/tests/streams/utils/cache" "github.com/trufnetwork/node/tests/streams/utils/procedure" "github.com/trufnetwork/node/tests/streams/utils/setup" "github.com/trufnetwork/sdk-go/core/util" @@ -20,7 +21,7 @@ import ( func TestResolutionInTransaction(t *testing.T) { // Use a fixed deployer address for consistency deployer := util.Unsafe_NewEthereumAddressFromString("0x0000000000000000000000000000000000000456") - cacheConfig := testutils.TestCache(deployer.Address(), "*") // Wildcard to cache all streams + cacheConfig := testutils.SimpleCache(deployer.Address(), "*") testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "resolution_transaction_test", @@ -28,7 +29,7 @@ func TestResolutionInTransaction(t *testing.T) { FunctionTests: []kwilTesting.TestFunc{ func(ctx context.Context, platform *kwilTesting.Platform) error { platform = procedure.WithSigner(platform, deployer.Bytes()) - helper := testutils.SetupCacheTest(ctx, platform, cacheConfig) + helper := cache.SetupCacheTest(ctx, platform, cacheConfig) defer helper.Cleanup() err := setup.CreateDataProvider(ctx, platform, deployer.Address()) @@ -57,7 +58,7 @@ func TestResolutionInTransaction(t *testing.T) { require.NoError(t, err) // Trigger resolution to discover the new stream - err = helper.TriggerResolution(ctx) + err = tn_cache.GetTestHelper().TriggerResolution(ctx) require.NoError(t, err) // Verify the stream was discovered and cached @@ -84,7 +85,7 @@ func TestResolutionInTransaction(t *testing.T) { assert.True(t, found, "Newly created stream should be resolved by wildcard") // Refresh cache for the discovered stream - _, err = helper.RefreshAllStreamsSync(ctx) + _, err = tn_cache.GetTestHelper().RefreshAllStreamsSync(ctx) require.NoError(t, err) }) diff --git a/tests/streams/utils/erc20/helper.go b/tests/streams/utils/erc20/helper.go index 91c3c21e1..c5e309bf9 100644 --- a/tests/streams/utils/erc20/helper.go +++ b/tests/streams/utils/erc20/helper.go @@ -1,169 +1,180 @@ -// Package erc20 provides helper functions for ERC-20 bridge testing +// Package erc20 provides helper functions for ERC-20 bridge testing. +// Follows the same patterns as query_test.go for consistency and compatibility. package erc20 import ( "context" "fmt" - "time" + "math/big" + "strconv" + "testing" "github.com/trufnetwork/kwil-db/common" - "github.com/trufnetwork/kwil-db/extensions/listeners" - _ "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" - _ "github.com/trufnetwork/kwil-db/node/exts/evm-sync" - _ "github.com/trufnetwork/kwil-db/node/exts/ordered-sync" + "github.com/trufnetwork/kwil-db/core/types" + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" kwilTesting "github.com/trufnetwork/kwil-db/testing" - "github.com/trufnetwork/node/tests/streams/utils/eventstore" - "github.com/trufnetwork/node/tests/streams/utils/service" ) -// ERC20BridgeTestHelper provides utilities for ERC-20 bridge testing -type ERC20BridgeTestHelper struct { - config *ERC20BridgeConfig - platform *kwilTesting.Platform - eventStore listeners.EventStore - service *common.Service - listenerCtx context.Context - cancel context.CancelFunc - listenerDone chan error - running bool -} +// WithERC20TestSetup is a helper function that sets up the test environment with ERC-20 bridge +// This version works with transaction-based isolation for perfect test isolation +func WithERC20TestSetup(escrowAddr string) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // Use the shared platform's DB and Engine (could be transaction-scoped) + app := &common.App{DB: platform.DB, Engine: platform.Engine} -// SetupERC20BridgeTest sets up ERC-20 bridge testing environment -func SetupERC20BridgeTest(ctx context.Context, platform *kwilTesting.Platform, config *ERC20BridgeConfig) (*ERC20BridgeTestHelper, error) { - helper := &ERC20BridgeTestHelper{ - config: config, - platform: platform, - listenerDone: make(chan error, 1), - } + // Reset the entire singleton to ensure test isolation + erc20shim.ForTestingResetSingleton() - // Create event store - helper.eventStore = eventstore.NewMockEventStore() + // Initialize extension with synced instance + _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, "sepolia", escrowAddr, "0x2222222222222222222222222222222222222222", 18) + if err != nil { + return fmt.Errorf("failed to force sync ERC20 instance for escrow %s: %w", escrowAddr, err) + } - // Create service with ERC-20 bridge configuration - bridgeConfig := config.BuildERC20BridgeConfig() - svc, err := service.CreateBridgeService(bridgeConfig) - if err != nil { - return nil, fmt.Errorf("failed to create service: %w", err) - } - helper.service = svc + err = erc20shim.ForTestingInitializeExtension(ctx, app) + if err != nil { + return fmt.Errorf("failed to initialize ERC20 extension: %w", err) + } - return helper, nil + return nil + } } -// StartERC20Listener manually starts the ERC-20 bridge listener -func (h *ERC20BridgeTestHelper) StartERC20Listener(ctx context.Context) error { - if h.running { - return fmt.Errorf("listener already running") - } +// WithERC20TestSetupTx is a transaction-aware version that works with WithTx for perfect isolation +func WithERC20TestSetupTx(escrowAddr string) func(t *testing.T, txPlatform *kwilTesting.Platform) { + return func(t *testing.T, txPlatform *kwilTesting.Platform) { + // Use the transaction-scoped platform + app := &common.App{DB: txPlatform.DB, Engine: txPlatform.Engine} - // Prefer mock listener if provided, otherwise fallback to registered listener - var listenerFunc listeners.ListenFunc - if mockFunc, hasMock := h.config.MockListeners["evm_sync"]; hasMock { - listenerFunc = mockFunc - } else { - lf, exists := listeners.GetListener("evm_sync") - if !exists { - return fmt.Errorf("evm_sync listener not registered - ensure kwil-db node/exts/evm-sync is imported") - } - listenerFunc = lf - } + // Step 1: Reset the entire singleton for this test + erc20shim.ForTestingResetSingleton() - // Create context with timeout - h.listenerCtx, h.cancel = context.WithTimeout(ctx, h.config.Timeout) + // Step 2: Initialize extension with synced instance using transaction-scoped DB + _, err := erc20shim.ForTestingForceSyncInstance(context.Background(), app, "sepolia", escrowAddr, "0x2222222222222222222222222222222222222222", 18) + if err != nil { + t.Fatalf("failed to force sync ERC20 instance for escrow %s: %v", escrowAddr, err) + } - // Start listener in background - h.running = true - go func() { - defer func() { h.running = false }() - err := listenerFunc(h.listenerCtx, h.service, h.eventStore) - h.listenerDone <- err - }() + // Step 3: Initialize extension with synced instance using transaction-scoped DB + err = erc20shim.ForTestingInitializeExtension(context.Background(), app) + if err != nil { + t.Fatalf("failed to initialize ERC20 extension: %v", err) + } - return nil + // Step 4: Set up cleanup to deactivate the instance after the test + // This ensures the next test can reuse the same escrow address + t.Cleanup(func() { + DeactivateCurrentInstanceTx(t, txPlatform, escrowAddr) + }) + } } -// StopERC20Listener stops the running listener -func (h *ERC20BridgeTestHelper) StopERC20Listener() error { - if !h.running { - return nil - } +// CreditUserBalance injects a realistic ERC-20 transfer to credit the user's balance. +// This simulates a user depositing tokens into the bridge. +func CreditUserBalance(ctx context.Context, platform *kwilTesting.Platform, escrowAddr, userAddr, amount string) error { + // Use the platform's DB and Engine (could be transaction-scoped) + app := &common.App{DB: platform.DB, Engine: platform.Engine} + return InjectERC20Transfer( + ctx, app, "sepolia", escrowAddr, "0x2222222222222222222222222222222222222222", userAddr, escrowAddr, amount, 10, nil) +} - if h.cancel != nil { - h.cancel() +// GetUserBalance queries the user's current balance via the extension. +func GetUserBalance(ctx context.Context, platform *kwilTesting.Platform, userAddr string) (string, error) { + txCtx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: 1}, + Signer: platform.Deployer, + Caller: "0x0000000000000000000000000000000000000000", + TxID: platform.Txid(), } + engCtx := &common.EngineContext{TxContext: txCtx, OverrideAuthz: true} - // Wait for listener to stop - select { - case err := <-h.listenerDone: - h.running = false - // Treat graceful cancellations as successful shutdowns - if err == context.Canceled || err == context.DeadlineExceeded { - return nil + var balance string + r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{userAddr}, func(row *common.Row) error { + if len(row.Values) != 1 { + return fmt.Errorf("expected 1 column, got %d", len(row.Values)) } - return err - case <-time.After(5 * time.Second): - return fmt.Errorf("listener did not stop within timeout") + balance = fmt.Sprintf("%v", row.Values[0]) + return nil + }) + if err != nil { + return "", fmt.Errorf("engine call error: %w", err) } + if r != nil && r.Error != nil { + return "", fmt.Errorf("engine execution error: %w", r.Error) + } + return balance, nil } -// Cleanup cleans up test resources -func (h *ERC20BridgeTestHelper) Cleanup() error { - return h.StopERC20Listener() -} - -// IsListenerRunning returns whether the listener is currently running -func (h *ERC20BridgeTestHelper) IsListenerRunning() bool { - return h.running -} - -// GetService returns the service instance for advanced testing -func (h *ERC20BridgeTestHelper) GetService() *common.Service { - return h.service -} +// CallLockAdmin executes the lock_admin system method with OverrideAuthz. +// This simulates an admin locking user tokens. +func CallLockAdmin(ctx context.Context, platform *kwilTesting.Platform, userAddr, amount string) error { + txCtx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: 1}, + Signer: platform.Deployer, + Caller: "0x0000000000000000000000000000000000000000", + TxID: platform.Txid(), + } + engCtx := &common.EngineContext{TxContext: txCtx, OverrideAuthz: true} -// GetEventStore returns the event store for advanced testing -func (h *ERC20BridgeTestHelper) GetEventStore() listeners.EventStore { - return h.eventStore -} + // Convert amount string to Decimal + amt, err := strconv.ParseInt(amount, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse amount: %w", err) + } + amtDec, err := types.NewDecimalFromBigInt(big.NewInt(amt), 0) + if err != nil { + return fmt.Errorf("failed to create decimal: %w", err) + } + amtDec.SetPrecisionAndScale(78, 0) -// WaitForListenerEvent waits for a specific event to be broadcast -func (h *ERC20BridgeTestHelper) WaitForListenerEvent(eventType string, timeout time.Duration) error { - if mockStore, ok := h.eventStore.(*eventstore.MockEventStore); ok { - return mockStore.WaitForEvent(eventType, timeout) + _, err = platform.Engine.Call(engCtx, platform.DB, "sepolia_bridge", "lock_admin", []any{userAddr, amtDec}, func(row *common.Row) error { + return nil + }) + if err != nil { + return fmt.Errorf("engine call error: %w", err) } - return fmt.Errorf("event store does not support event waiting") + return nil } -// WithERC20Bridge is a test wrapper that sets up and tears down ERC-20 bridge testing -func WithERC20Bridge(t TestingT, ctx context.Context, platform *kwilTesting.Platform, config *ERC20BridgeConfig, testFunc func(*ERC20BridgeTestHelper)) { - helper, err := SetupERC20BridgeTest(ctx, platform, config) +// DeactivateCurrentInstanceTx deactivates the ERC20 instance for the given escrow. +// This should be called in test cleanup to ensure the next test can use the same escrow. +// It sets active=false in the DB and refreshes the singleton state. +func DeactivateCurrentInstanceTx(t *testing.T, txPlatform *kwilTesting.Platform, escrowAddr string) { + app := &common.App{DB: txPlatform.DB, Engine: txPlatform.Engine} + + // Get the instance ID by calling ForTestingForceSyncInstance (it's idempotent) + id, err := erc20shim.ForTestingForceSyncInstance(context.Background(), app, "sepolia", escrowAddr, "0x2222222222222222222222222222222222222222", 18) if err != nil { - t.Fatalf("Failed to setup ERC-20 bridge: %v", err) + t.Logf("Warning: failed to get instance ID for deactivation: %v", err) + return } - defer func() { - if err := helper.Cleanup(); err != nil { - t.Errorf("Failed to cleanup ERC-20 bridge: %v", err) - } - }() - // Auto-start listener if configured - if config.AutoStart { - if err := helper.StartERC20Listener(ctx); err != nil { - t.Fatalf("Failed to start ERC-20 listener: %v", err) - } + // Call the meta extension's disable method to set active=false + txCtx := &common.TxContext{ + Ctx: context.Background(), + BlockContext: &common.BlockContext{Height: 1}, + Signer: txPlatform.Deployer, + Caller: "0x0000000000000000000000000000000000000000", + TxID: txPlatform.Txid(), } + engCtx := &common.EngineContext{TxContext: txCtx, OverrideAuthz: true} - testFunc(helper) -} - -// WithERC20BridgeAndListener combines setup and auto-start -func WithERC20BridgeAndListener(t TestingT, ctx context.Context, platform *kwilTesting.Platform, config *ERC20BridgeConfig, testFunc func(*ERC20BridgeTestHelper)) { - config.AutoStart = true - WithERC20Bridge(t, ctx, platform, config, testFunc) -} + // Call the meta extension's disable method + _, err = txPlatform.Engine.Call(engCtx, txPlatform.DB, "kwil_erc20_meta", "disable", []any{id}, func(row *common.Row) error { + return nil + }) + if err != nil { + t.Logf("Warning: failed to deactivate instance %s: %v", id, err) + // Don't fail the test for cleanup issues, just log + } -// TestingT interface for test functions -type TestingT interface { - Fatalf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) + // Reset and re-initialize the singleton to reflect the deactivated state + erc20shim.ForTestingResetSingleton() + err = erc20shim.ForTestingInitializeExtension(context.Background(), app) + if err != nil { + t.Logf("Warning: failed to re-initialize singleton after deactivation: %v", err) + // Don't fail the test for cleanup issues, just log + } } diff --git a/tests/streams/utils/erc20/inject.go b/tests/streams/utils/erc20/inject.go index d8c9a9963..e513bce08 100644 --- a/tests/streams/utils/erc20/inject.go +++ b/tests/streams/utils/erc20/inject.go @@ -1,5 +1,3 @@ -//go:build kwiltest - package erc20 import ( diff --git a/tests/streams/utils/runner.go b/tests/streams/utils/runner.go index 25a476f88..cb92c5413 100644 --- a/tests/streams/utils/runner.go +++ b/tests/streams/utils/runner.go @@ -77,7 +77,8 @@ func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { }, kwilOpts) } -// wrapWithExtensionsSetup wraps test functions with both cache and ERC-20 bridge initialization +// wrapWithExtensionsSetup wraps test functions with cache extension initialization +// ERC20 setup removed - now handled directly in tests using WithERC20TestSetup func wrapWithExtensionsSetup(ctx context.Context, originalFuncs []kwilTesting.TestFunc, cacheConfig *cache.CacheOptions, erc20Config *erc20.ERC20BridgeConfig, opts *kwilTesting.Options) []kwilTesting.TestFunc { wrapped := make([]kwilTesting.TestFunc, len(originalFuncs)) for i, fn := range originalFuncs { @@ -96,15 +97,7 @@ func wrapWithExtensionsSetup(ctx context.Context, originalFuncs []kwilTesting.Te cleanups = append(cleanups, cleanup) } - // Setup ERC-20 bridge if configured - if erc20Config != nil { - cleanup, err := setupERC20Bridge(ctx, erc20Config, platform) - if err != nil { - testErr = fmt.Errorf("failed to setup ERC-20 bridge: %w", err) - return - } - cleanups = append(cleanups, cleanup) - } + // ERC-20 bridge setup removed - handle directly in tests with WithERC20TestSetup // Run original test err := originalFn(ctx, platform) @@ -167,26 +160,7 @@ func setupCacheExtension(ctx context.Context, cacheConfig *cache.CacheOptions, p } // setupERC20Bridge sets up the ERC-20 bridge for testing -func setupERC20Bridge(ctx context.Context, erc20Config *erc20.ERC20BridgeConfig, platform *kwilTesting.Platform) (func(), error) { - // Setup ERC-20 bridge test - bridgeHelper, err := erc20.SetupERC20BridgeTest(ctx, platform, erc20Config) - if err != nil { - return nil, fmt.Errorf("failed to setup ERC-20 bridge test: %w", err) - } - - // Auto-start listener if configured - if erc20Config.AutoStart { - if startErr := bridgeHelper.StartERC20Listener(ctx); startErr != nil { - return nil, fmt.Errorf("failed to start ERC-20 listener: %w", startErr) - } - } - - cleanup := func() { - _ = bridgeHelper.Cleanup() - } - - return cleanup, nil -} +// ERC20 setup removed - now handled directly in tests using WithERC20TestSetup // TestingT interface for test functions type TestingT interface { @@ -194,29 +168,4 @@ type TestingT interface { Errorf(format string, args ...interface{}) } -// WithExtensions is a convenience wrapper for both cache and ERC-20 bridge testing -func WithExtensions(t TestingT, ctx context.Context, platform *kwilTesting.Platform, cacheConfig *cache.CacheOptions, erc20Config *erc20.ERC20BridgeConfig, testFunc func(*cache.CacheTestHelper, *erc20.ERC20BridgeTestHelper)) { - // Setup cache if configured - var cacheHelper *cache.CacheTestHelper - if cacheConfig != nil { - cacheHelper = cache.SetupCacheTest(ctx, platform, cacheConfig) - defer cacheHelper.Cleanup() - } - - // Setup ERC-20 bridge if configured - var bridgeHelper *erc20.ERC20BridgeTestHelper - if erc20Config != nil { - var err error - bridgeHelper, err = erc20.SetupERC20BridgeTest(ctx, platform, erc20Config) - if err != nil { - t.Fatalf("Failed to setup ERC-20 bridge: %v", err) - } - defer func() { - if err := bridgeHelper.Cleanup(); err != nil { - t.Errorf("Failed to cleanup ERC-20 bridge: %v", err) - } - }() - } - - testFunc(cacheHelper, bridgeHelper) -} +// WithExtensions removed - ERC20 setup now handled directly in tests diff --git a/tests/streams/utils/utils.go b/tests/streams/utils/utils.go index 84804eb9f..32b61a803 100644 --- a/tests/streams/utils/utils.go +++ b/tests/streams/utils/utils.go @@ -9,14 +9,12 @@ package testutils import ( "time" - "github.com/trufnetwork/kwil-db/extensions/listeners" "github.com/trufnetwork/kwil-db/extensions/precompiles" kwilTesting "github.com/trufnetwork/kwil-db/testing" // Extension registration "github.com/trufnetwork/node/extensions/tn_cache" "github.com/trufnetwork/node/tests/streams/utils/cache" - "github.com/trufnetwork/node/tests/streams/utils/erc20" ) // init registers tn_cache precompiles globally for tests @@ -34,9 +32,8 @@ func init() { // Type Aliases for backward compatibility type CacheOptions = cache.CacheOptions type StreamConfig = cache.StreamConfig -type ERC20BridgeConfig = erc20.ERC20BridgeConfig -type ERC20ChainConfig = erc20.ERC20ChainConfig -type ERC20BridgeTestHelper = erc20.ERC20BridgeTestHelper + +// ERC20 types removed - using simple pattern instead of complex fixtures type CacheTestHelper = cache.CacheTestHelper // ============================================================================ @@ -50,8 +47,7 @@ func NewCacheOptions() *CacheOptions { return cache.NewCacheOptions() } // ERC-20 FUNCTIONS (RE-EXPORTS) // ============================================================================ -// ERC-20 configuration builders -func NewERC20BridgeConfig() *ERC20BridgeConfig { return erc20.NewERC20BridgeConfig() } +// ERC-20 configuration builders removed - using simple pattern // ============================================================================ // CONVENIENCE FUNCTIONS @@ -78,35 +74,6 @@ func GetTestOptionsWithCache(cacheOpts ...*CacheOptions) *Options { return opts } -func GetTestOptionsWithERC20Bridge(erc20Opts *ERC20BridgeConfig) *Options { - return &Options{ - Options: &kwilTesting.Options{ - UseTestContainer: true, - }, - ERC20Bridge: erc20Opts, - } -} - -func GetTestOptionsWithBoth(cacheOpts *CacheOptions, erc20Opts *ERC20BridgeConfig) *Options { - opts := &Options{ - Options: &kwilTesting.Options{ - UseTestContainer: true, - }, - } - - if cacheOpts != nil { - opts.Cache = cacheOpts - opts.DisableCache = false - } - - if erc20Opts != nil { - opts.ERC20Bridge = erc20Opts - opts.DisableERC20Bridge = false - } - - return opts -} - func SimpleCache(dataProvider, streamID string) *CacheOptions { return NewCacheOptions(). WithEnabled(). @@ -115,23 +82,7 @@ func SimpleCache(dataProvider, streamID string) *CacheOptions { WithStream(dataProvider, streamID, "0 0 31 2 *") // Never auto-refresh (Feb 31st) } -func SimpleERC20Bridge(rpcURL, signerKey string) *ERC20BridgeConfig { - return NewERC20BridgeConfig(). - WithRPC("sepolia", rpcURL). - WithSigner("test_bridge", signerKey) -} - -func MockERC20Bridge(mockListener listeners.ListenFunc) *ERC20BridgeConfig { - return NewERC20BridgeConfig(). - WithRPC("mock", "ws://mock-rpc"). - WithSigner("mock_bridge", "/dev/null"). - WithMockListener("evm_sync", mockListener) -} - -// MockERC20Listener creates a mock listener for testing ERC-20 bridge functionality -func MockERC20Listener(expectedEvents []string) listeners.ListenFunc { - return erc20.MockERC20Listener(expectedEvents) -} +// ERC-20 functions removed - using simple pattern instead of complex configurations // ============================================================================ // UTILITY FUNCTIONS From aa73919505534277cfaad4a52bfc2d1c58160f82 Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Wed, 10 Sep 2025 16:32:02 -0300 Subject: [PATCH 06/18] feat(tests): add comprehensive ERC-20 bridge tests for various functionalities This commit introduces multiple new test files for the ERC-20 bridge functionality, enhancing the test coverage and reliability of the system. Key additions include: - `erc20_bridge_actions_smoke_test.go`: A minimal smoke test to verify basic ERC-20 actions and expected values. - `erc20_bridge_admin_authz_test.go`: A test to validate SYSTEM-only method access control, ensuring proper authorization handling. - `erc20_bridge_end_to_end_test.go`: An end-to-end test to confirm the complete flow from deposit to reward listing. - `erc20_bridge_epoch_test.go`: A test for validating the lock-and-issue, finalize, and confirm processes during epoch transitions. - `erc20_bridge_injection_test.go`: A test to ensure injected transfers affect user balances correctly. - `erc20_bridge_multi_instance_test.go`: A test to verify isolation of balances across multiple ERC-20 instances. - `erc20_bridge_transfer_test.go`: A test to validate the transfer functionality between users. These changes aim to improve the clarity, maintainability, and robustness of the ERC-20 bridge testing framework. --- tests/extensions/erc20/common_test.go | 165 ++++++++++++++++++ .../erc20/erc20_bridge_actions_smoke_test.go | 120 +++++++++++++ .../erc20/erc20_bridge_admin_authz_test.go | 80 +++++++++ .../erc20/erc20_bridge_end_to_end_test.go | 85 +++++++++ .../erc20/erc20_bridge_epoch_test.go | 157 ++++++++--------- .../erc20/erc20_bridge_injection_test.go | 101 ++++++----- .../erc20/erc20_bridge_multi_instance_test.go | 110 ++++++++++++ .../erc20/erc20_bridge_transfer_test.go | 76 ++++++++ tests/streams/utils/runner.go | 6 + 9 files changed, 768 insertions(+), 132 deletions(-) create mode 100644 tests/extensions/erc20/common_test.go create mode 100644 tests/extensions/erc20/erc20_bridge_actions_smoke_test.go create mode 100644 tests/extensions/erc20/erc20_bridge_admin_authz_test.go create mode 100644 tests/extensions/erc20/erc20_bridge_end_to_end_test.go create mode 100644 tests/extensions/erc20/erc20_bridge_multi_instance_test.go create mode 100644 tests/extensions/erc20/erc20_bridge_transfer_test.go diff --git a/tests/extensions/erc20/common_test.go b/tests/extensions/erc20/common_test.go new file mode 100644 index 000000000..b9c4da4f1 --- /dev/null +++ b/tests/extensions/erc20/common_test.go @@ -0,0 +1,165 @@ +package tests + +import ( + "context" + "fmt" + "path/filepath" + "runtime" + "testing" + + "github.com/trufnetwork/kwil-db/common" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/node/internal/migrations" + testutils "github.com/trufnetwork/node/tests/streams/utils" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" +) + +// Common deterministic values used across ERC20 bridge tests +const ( + TestChain = "sepolia" + TestEscrowA = "0x1111111111111111111111111111111111111111" + TestEscrowB = "0x2222222222222222222222222222222222222222" + TestERC20 = "0x2222222222222222222222222222222222222222" + TestUserA = "0xabc0000000000000000000000000000000000001" + TestUserB = "0xabc0000000000000000000000000000000000002" + TestAmount1 = "1000000000000000000" // 1.0 tokens + TestAmount2 = "2000000000000000000" // 2.0 tokens +) + +// seedAndRun is a helper that handles seed script setup and runs the test with proper isolation +func seedAndRun(t TestingT, name string, extraSeed string, fn kwilTesting.TestFunc) { + seedScripts := migrations.GetSeedScriptPaths() + + if extraSeed != "" { + _, thisFile, _, _ := runtime.Caller(1) + extraSeedPath := filepath.Join(filepath.Dir(thisFile), extraSeed) + seedScripts = append(seedScripts, extraSeedPath) + } + + // Wrap the test function to add singleton reset and cleanup + wrappedFn := func(ctx context.Context, platform *kwilTesting.Platform) error { + // For tests that DO NOT use simple_mock.sql, allow deactivation to ensure clean slate + if extraSeed != "simple_mock.sql" { + if err := deactivateSeededInstance(ctx, platform, extraSeed); err != nil { + return fmt.Errorf("failed to deactivate seeded instance: %w", err) + } + } + + // Enable idempotent prepare for active instances during test + erc20shim.ForTestingAllowPrepareOnActive(true) + t.Cleanup(func() { erc20shim.ForTestingAllowPrepareOnActive(false) }) + + // Add cleanup for seeded instances + addSeedCleanup(t, platform, extraSeed) + + // Run the actual test inside a transaction to ensure rollback isolation + tx, err := platform.DB.BeginTx(ctx) + if err != nil { + return fmt.Errorf("begin tx: %w", err) + } + defer tx.Rollback(ctx) + + txPlatform := &kwilTesting.Platform{ + Engine: platform.Engine, + DB: tx, + Deployer: platform.Deployer, + Logger: platform.Logger, + } + + return fn(ctx, txPlatform) + } + + testutils.RunSchemaTest(t.(*testing.T), kwilTesting.SchemaTest{ + Name: name, + SeedScripts: seedScripts, + FunctionTests: []kwilTesting.TestFunc{wrappedFn}, + }, &testutils.Options{Options: testutils.GetTestOptions()}) +} + +// engCtx creates a standard EngineContext for testing +func engCtx(ctx context.Context, platform *kwilTesting.Platform, caller string, height int64, overrideAuthz bool) *common.EngineContext { + return &common.EngineContext{ + TxContext: &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: height}, + Signer: platform.Deployer, + Caller: caller, + TxID: platform.Txid(), + }, + OverrideAuthz: overrideAuthz, + } +} + +// TestingT interface for test functions (matches testutils) +type TestingT interface { + Fatalf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + Cleanup(func()) +} + +// ensureSingletonReset ensures the ERC20 singleton is reset and initialized for test isolation +func ensureSingletonReset(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + + // Reset singleton to ensure clean state + erc20shim.ForTestingResetSingleton() + + // Initialize extension to load any DB state into singleton + err := erc20shim.ForTestingInitializeExtension(ctx, app) + if err != nil { + return fmt.Errorf("failed to initialize ERC20 extension: %w", err) + } + + return nil +} + +// addSeedCleanup adds cleanup to deactivate instances used in seeds +func addSeedCleanup(t TestingT, platform *kwilTesting.Platform, extraSeed string) { + // If using simple_mock.sql, add cleanup to deactivate the seeded instance + if extraSeed == "simple_mock.sql" { + t.Cleanup(func() { + testerc20.DeactivateCurrentInstanceTx(t.(*testing.T), platform, TestEscrowA) + }) + } +} + +// deactivateSeededInstance deactivates the instance created by simple_mock.sql if it exists +func deactivateSeededInstance(ctx context.Context, platform *kwilTesting.Platform, extraSeed string) error { + if extraSeed == "simple_mock.sql" { + // Try to deactivate the seeded instance to allow reuse + app := &common.App{DB: platform.DB, Engine: platform.Engine} + id, err := erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, TestEscrowA, TestERC20, 18) + if err != nil { + // Instance might not exist or might already be deactivated, ignore error + return nil + } + + // Call the meta extension's disable method + txCtx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: 1}, + Signer: platform.Deployer, + Caller: "0x0000000000000000000000000000000000000000", + TxID: platform.Txid(), + } + engCtx := &common.EngineContext{TxContext: txCtx, OverrideAuthz: true} + + _, err = platform.Engine.Call(engCtx, platform.DB, "kwil_erc20_meta", "disable", []any{id}, func(row *common.Row) error { + return nil + }) + if err != nil { + // Instance might already be deactivated, ignore error + return nil + } + + // Reset and re-initialize the singleton to reflect the deactivated state + erc20shim.ForTestingResetSingleton() + err = erc20shim.ForTestingInitializeExtension(ctx, app) + if err != nil { + return fmt.Errorf("failed to re-initialize singleton: %w", err) + } + } + return nil +} diff --git a/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go new file mode 100644 index 000000000..1396e5c17 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + "github.com/trufnetwork/kwil-db/core/types" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" +) + +// TestERC20BridgeActionsSmoke is a minimal actions smoke test. +// Goal: prove node can call named erc20 actions and receive sensible values. +// Scope: do not re-test Kwil internals; only verify building blocks needed by node logic. +// +// Test flow: +// 1) Initialize ERC20 instance via test shims (ForTestingForceSyncInstance + ForTestingInitializeExtension) +// 2) Call erc20.info() and assert chain/escrow/period/synced fields are set +// 3) Call erc20.decimals() and assert expected 18 +// 4) Call erc20.scale_up("1") and expect 1000000000000000000 (1 * 10^18) +// 5) Call erc20.scale_down(1e18) and expect "1" +func TestERC20BridgeActionsSmoke(t *testing.T) { + seedAndRun(t, "erc20_bridge_actions_smoke", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + + // Singleton reset and initialize is handled by seedAndRun + // The seeded instance may have been deactivated, so we need to reactivate it + _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, TestEscrowA, TestERC20, 18) + require.NoError(t, err) + + err = erc20shim.ForTestingInitializeExtension(ctx, app) + require.NoError(t, err) + + // Test erc20.info() action + engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 1, false) + + var chainResult, escrowResult, periodResult string + var syncedResult bool + r, err := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "info", []any{}, func(row *common.Row) error { + if len(row.Values) < 4 { + return nil // Skip if not enough fields + } + chainResult = row.Values[0].(string) + escrowResult = row.Values[1].(string) + periodResult = row.Values[2].(string) + syncedResult = row.Values[6].(bool) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + // Assert info fields are properly set + require.Equal(t, TestChain, chainResult, "info should return expected chain") + require.Equal(t, TestEscrowA, escrowResult, "info should return expected escrow") + require.NotEmpty(t, periodResult, "info should return non-empty period") + require.True(t, syncedResult, "instance should be synced after force sync") + + // Test erc20.decimals() action + var decimalsResult int64 + r, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "decimals", []any{}, func(row *common.Row) error { + if len(row.Values) != 1 { + return nil + } + decimalsResult = row.Values[0].(int64) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + require.Equal(t, int64(18), decimalsResult, "decimals should return 18 for ERC20") + + // Test erc20.scale_up("1") action + var scaledUpResult *types.Decimal + r, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "scale_up", []any{"1"}, func(row *common.Row) error { + if len(row.Values) != 1 { + return nil + } + scaledUpResult = row.Values[0].(*types.Decimal) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + require.Equal(t, TestAmount1, scaledUpResult.String(), "scale_up(1) should return 1e18") + + // Test erc20.scale_down(1e18) action + var scaledDownResult string + // scale_down meta handler expects: UUID + *types.Decimal + // makeMetaHandler prepends &id, so we pass amount as *types.Decimal + amountDecimal, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) + require.NoError(t, err) + + r, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "scale_down", []any{amountDecimal}, func(row *common.Row) error { + if len(row.Values) != 1 { + return nil + } + scaledDownResult = row.Values[0].(string) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + if !(scaledDownResult == "1" || scaledDownResult == "1.000000000000000000") { + require.Failf(t, "unexpected scale_down result", "got %s, expected 1 or 1.000000000000000000", scaledDownResult) + } + + return nil + }) +} diff --git a/tests/extensions/erc20/erc20_bridge_admin_authz_test.go b/tests/extensions/erc20/erc20_bridge_admin_authz_test.go new file mode 100644 index 000000000..c546b7934 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_admin_authz_test.go @@ -0,0 +1,80 @@ +package tests + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + + "github.com/trufnetwork/kwil-db/core/types" + + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" +) + +// TestERC20BridgeAdminAuthz is a minimal SYSTEM auth boundary test. +// Goal: ensure SYSTEM-only method lock_admin fails without OverrideAuthz and succeeds with it. +// Scope: avoid deep permission trees; just validate the two contrasting outcomes. +// +// Test flow: +// 1) Seed + init instance. +// 2) Inject deposit so user has balance. +// 3) Attempt lock_admin without OverrideAuthz: expect execution error. +// 4) Retry with OverrideAuthz: expect success and balance changes to reflect lock. +func TestERC20BridgeAdminAuthz(t *testing.T) { + seedAndRun(t, "erc20_bridge_admin_authz", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + + // Singleton reset and initialize is handled by seedAndRun + // Use the sepolia_bridge alias created by simple_mock.sql + err := erc20shim.ForTestingInitializeExtension(ctx, app) + require.NoError(t, err) + + // Step 1: Inject deposit so user has balance + err = testerc20.InjectERC20Transfer(ctx, app, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount2, 10, nil) + require.NoError(t, err) + + // Verify user has the balance + balance, err := testerc20.GetUserBalance(ctx, platform, TestUserA) + require.NoError(t, err) + require.Equal(t, TestAmount2, balance, "user should have deposit amount") + + // Step 2: Attempt lock_admin without OverrideAuthz - should fail + engineCtxFail := engCtx(ctx, platform, TestUserA, 2, false) + + // lock_admin expects amount as numeric(78,0) + amtDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) + require.NoError(t, err) + + _, err = platform.Engine.Call(engineCtxFail, platform.DB, "sepolia_bridge", "lock_admin", []any{TestUserA, amtDec}, func(row *common.Row) error { + return nil + }) + // Should fail with authz error (SYSTEM method called without OverrideAuthz) + require.Error(t, err, "action lock_admin is system-only") + + // Balance should remain unchanged + balance, err = testerc20.GetUserBalance(ctx, platform, TestUserA) + require.NoError(t, err) + require.Equal(t, TestAmount2, balance, "balance should remain unchanged after failed lock_admin") + + // Step 3: Retry lock_admin WITH OverrideAuthz - should succeed + engineCtxSuccess := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 3, true) + + r, err := platform.Engine.Call(engineCtxSuccess, platform.DB, "sepolia_bridge", "lock_admin", []any{TestUserA, amtDec}, func(row *common.Row) error { + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + // Step 4: Verify balance was reduced after successful lock_admin + balance, err = testerc20.GetUserBalance(ctx, platform, TestUserA) + require.NoError(t, err) + require.Equal(t, TestAmount1, balance, "balance should be reduced after successful lock_admin") + + return nil + }) +} diff --git a/tests/extensions/erc20/erc20_bridge_end_to_end_test.go b/tests/extensions/erc20/erc20_bridge_end_to_end_test.go new file mode 100644 index 000000000..2c530a5aa --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_end_to_end_test.go @@ -0,0 +1,85 @@ +package tests + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + + "github.com/trufnetwork/kwil-db/core/types" + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" + deverc20 "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20/dev" +) + +// TestERC20BridgeEndToEnd is a minimal E2E test using only actions + shims. +// Goal: deposit -> bridge -> finalize/confirm -> list_wallet_rewards shows reward row(s). +// Scope: prove happy-path building blocks; avoid deep verification of internals. +// +// Test flow: +// 1) Seed + init instance; set small distribution period with ForTestingSetDistributionPeriod. +// 2) Inject deposit to give the user a balance. +// 3) Call alias.bridge(amount) as user to lock+issue into epoch. +// 4) Finalize current epoch and create next (ForTestingFinalizeCurrentEpoch). +// 5) Confirm finalized epochs (ForTestingConfirmAllFinalizedEpochs). +// 6) Call alias.list_wallet_rewards(user,false) and assert at least one row returned. +func TestERC20BridgeEndToEnd(t *testing.T) { + seedAndRun(t, "erc20_bridge_end_to_end", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + + // Singleton reset and initialize is handled by seedAndRun + // Use the sepolia_bridge alias created by simple_mock.sql + + // Set small distribution period for quick epoch progression using dev helper + require.NoError(t, deverc20.SetDistributionPeriod(ctx, app, TestChain, TestEscrowA, 1)) + + err := erc20shim.ForTestingInitializeExtension(ctx, app) + require.NoError(t, err) + + // Step 1: Inject deposit to give user a balance + err = testerc20.InjectERC20Transfer(ctx, app, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount1, 10, nil) + require.NoError(t, err) + + // Verify user has the balance + balance, err := testerc20.GetUserBalance(ctx, platform, TestUserA) + require.NoError(t, err) + require.Equal(t, TestAmount1, balance, "user should have deposit amount") + + // Step 2: Call bridge action as user to lock+issue into epoch + engineCtx := engCtx(ctx, platform, TestUserA, 2, false) + + amtDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) + require.NoError(t, err) + _, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "bridge", []any{amtDec}, func(row *common.Row) error { + return nil + }) + require.NoError(t, err) + + // Step 3: Finalize current epoch and create next + var bh [32]byte + require.NoError(t, erc20shim.ForTestingFinalizeCurrentEpoch(ctx, app, TestChain, TestEscrowA, 11, bh)) + + // Step 4: Confirm finalized epochs + require.NoError(t, erc20shim.ForTestingConfirmAllFinalizedEpochs(ctx, app, TestChain, TestEscrowA)) + + // Step 5: Query wallet rewards to verify bridge flow worked + engineCtx = engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 3, false) + + rewardRows := 0 + r, err := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "list_wallet_rewards", []any{TestUserA, false}, func(row *common.Row) error { + rewardRows++ + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + // Assert that at least one reward row was returned + require.Greater(t, rewardRows, 0, "user should have at least one wallet reward after bridge flow") + + return nil + }) +} diff --git a/tests/extensions/erc20/erc20_bridge_epoch_test.go b/tests/extensions/erc20/erc20_bridge_epoch_test.go index bc307dddc..738d8c9ad 100644 --- a/tests/extensions/erc20/erc20_bridge_epoch_test.go +++ b/tests/extensions/erc20/erc20_bridge_epoch_test.go @@ -1,100 +1,95 @@ -//go:build kwiltest - package tests import ( "context" - "path/filepath" - "runtime" + "fmt" "testing" "github.com/stretchr/testify/require" "github.com/trufnetwork/kwil-db/common" kwilTesting "github.com/trufnetwork/kwil-db/testing" - "github.com/trufnetwork/node/internal/migrations" - testutils "github.com/trufnetwork/node/tests/streams/utils" testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" ethcommon "github.com/ethereum/go-ethereum/common" erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" - orderedsync "github.com/trufnetwork/kwil-db/node/exts/ordered-sync" ) // TestERC20BridgeEpochFlow validates lock-and-issue, finalize and confirm using shims. func TestERC20BridgeEpochFlow(t *testing.T) { - _, thisFile, _, _ := runtime.Caller(0) - seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") - - seedScripts := migrations.GetSeedScriptPaths() - seedScripts = append(seedScripts, seedPath) - - testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: "erc20_bridge_epoch_flow", - SeedScripts: seedScripts, - FunctionTests: []kwilTesting.TestFunc{ - func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - // ensure ordered-sync namespace - require.NoError(t, orderedsync.ForTestingEnsureNamespace(ctx, app)) - - // Note: Global state reset should be handled by test framework, not individual tests - chain := "sepolia" - escrow := "0xdddddddddddddddddddddddddddddddddddddddd" - erc20 := "0x2222222222222222222222222222222222222222" - user := "0xabc0000000000000000000000000000000000001" - value := "500000000000000000" // 0.5 - - // Ensure instance synced (register topic and create DB instance/epoch if missing) - _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) - require.NoError(t, err) - - // Make distribution period small for test determinism - require.NoError(t, erc20shim.ForTestingSetDistributionPeriod(ctx, app, chain, escrow, 1)) - - // Credit balance via injected transfer (simulates inbound deposit) - require.NoError(t, testerc20.InjectERC20Transfer(ctx, app, chain, escrow, erc20, user, escrow, value, 10, nil)) - - // Lock and issue directly into epoch (simulate bridge request) - require.NoError(t, erc20shim.ForTestingLockAndIssueDirect(ctx, app, chain, escrow, user, value)) - - // Assert pre-finalize: there should be a pending reward in epoch_rewards - preQ := ` - {kwil_erc20_meta}SELECT count(*) FROM epoch_rewards` - var preRows int - err = platform.Engine.ExecuteWithoutEngineCtx(ctx, platform.DB, preQ, nil, func(row *common.Row) error { - if len(row.Values) != 1 { - return nil - } - preRows = int(row.Values[0].(int64)) - return nil - }) - require.NoError(t, err) - require.Greater(t, preRows, 0, "expected pending epoch reward before finalize") - - // Finalize current epoch and create next - var bh [32]byte - require.NoError(t, erc20shim.ForTestingFinalizeCurrentEpoch(ctx, app, chain, escrow, 11, bh)) - - // Confirm finalized epochs - require.NoError(t, erc20shim.ForTestingConfirmAllFinalizedEpochs(ctx, app, chain, escrow)) - - // Query confirmed rewards directly - q := ` - {kwil_erc20_meta}SELECT r.recipient, r.amount FROM epoch_rewards r - JOIN epochs e ON e.id = r.epoch_id - WHERE e.confirmed IS TRUE AND r.recipient = $recipient - ` - var rows int - ubytes := ethcommon.HexToAddress(user).Bytes() - err = platform.Engine.ExecuteWithoutEngineCtx(ctx, platform.DB, q, map[string]any{"recipient": ubytes}, func(row *common.Row) error { - rows++ - return nil - }) - require.NoError(t, err) - require.Greater(t, rows, 0, "expected confirmed wallet reward rows") - + seedAndRun(t, "erc20_bridge_epoch_flow", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + + // Singleton reset and initialize is handled by seedAndRun + // Use the sepolia_bridge alias created by simple_mock.sql, but create a separate instance for this test + + // Use a different escrow for this test to avoid conflicts with seeded instance + chain := "sepolia" + escrow := "0xdddddddddddddddddddddddddddddddddddddddd" + erc20 := "0x2222222222222222222222222222222222222222" + user := "0xabc0000000000000000000000000000000000001" + value := "500000000000000000" // 0.5 + + // Ensure instance synced (register topic and create DB instance/epoch if missing) + _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) + require.NoError(t, err) + + // Make distribution period small for test determinism + require.NoError(t, erc20shim.ForTestingSetDistributionPeriod(ctx, app, chain, escrow, 1)) + + // Create an alias for this instance + err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` + USE erc20 { + chain: '%s', + escrow: '%s' + } AS epoch_test_bridge + `, chain, escrow), nil, nil) + require.NoError(t, err) + + // Credit balance via injected transfer (simulates inbound deposit) + require.NoError(t, testerc20.InjectERC20Transfer(ctx, app, chain, escrow, erc20, user, escrow, value, 10, nil)) + + // Lock and issue directly into epoch (simulate bridge request) + require.NoError(t, erc20shim.ForTestingLockAndIssueDirect(ctx, app, chain, escrow, user, value)) + + // Assert pre-finalize: there should be a pending reward in epoch_rewards + preQ := ` + {kwil_erc20_meta}SELECT count(*) FROM epoch_rewards` + var preRows int + err = platform.Engine.ExecuteWithoutEngineCtx(ctx, platform.DB, preQ, nil, func(row *common.Row) error { + if len(row.Values) != 1 { return nil - }, - }, - }, &testutils.Options{Options: testutils.GetTestOptions()}) + } + preRows = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err) + require.Greater(t, preRows, 0, "expected pending epoch reward before finalize") + + // Finalize current epoch and create next + var bh [32]byte + require.NoError(t, erc20shim.ForTestingFinalizeCurrentEpoch(ctx, app, chain, escrow, 11, bh)) + + // Confirm finalized epochs + require.NoError(t, erc20shim.ForTestingConfirmAllFinalizedEpochs(ctx, app, chain, escrow)) + + // Query confirmed rewards directly + q := ` + {kwil_erc20_meta}SELECT r.recipient, r.amount FROM epoch_rewards r + JOIN epochs e ON e.id = r.epoch_id + WHERE e.confirmed IS TRUE AND r.recipient = $recipient + ` + var rows int + ubytes := ethcommon.HexToAddress(user).Bytes() + err = platform.Engine.ExecuteWithoutEngineCtx(ctx, platform.DB, q, map[string]any{"recipient": ubytes}, func(row *common.Row) error { + rows++ + return nil + }) + require.NoError(t, err) + require.Greater(t, rows, 0, "expected confirmed wallet reward rows") + + // Cleanup: Deactivate the test instance + testerc20.DeactivateCurrentInstanceTx(t, platform, escrow) + + return nil + }) } diff --git a/tests/extensions/erc20/erc20_bridge_injection_test.go b/tests/extensions/erc20/erc20_bridge_injection_test.go index e6a69cbcd..ca6fd9b1f 100644 --- a/tests/extensions/erc20/erc20_bridge_injection_test.go +++ b/tests/extensions/erc20/erc20_bridge_injection_test.go @@ -3,15 +3,11 @@ package tests import ( "context" "fmt" - "path/filepath" - "runtime" "testing" "github.com/stretchr/testify/require" "github.com/trufnetwork/kwil-db/common" kwilTesting "github.com/trufnetwork/kwil-db/testing" - "github.com/trufnetwork/node/internal/migrations" - testutils "github.com/trufnetwork/node/tests/streams/utils" testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" @@ -19,58 +15,61 @@ import ( // TestERC20BridgeInjectedTransferAffectsBalance uses the production code path via ordered-sync/evm-sync shims. func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { - // seed simple bridge action - _, thisFile, _, _ := runtime.Caller(0) - seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") + seedAndRun(t, "erc20_bridge_injected_transfer_affects_balance", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} - seedScripts := migrations.GetSeedScriptPaths() - seedScripts = append(seedScripts, seedPath) + // Use a different escrow for this test to avoid conflicts with seeded instance + chain := "sepolia" + escrow := "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + erc20 := "0x2222222222222222222222222222222222222222" + user := "0xabc0000000000000000000000000000000000001" + value := "1000000000000000000" - testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: "erc20_bridge_injected_transfer_affects_balance", - SeedScripts: seedScripts, - FunctionTests: []kwilTesting.TestFunc{ - func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - // ensure ordered-sync topic and meta schema via helpers - chain := "sepolia" - escrow := "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" - erc20 := "0x2222222222222222222222222222222222222222" - user := "0xabc0000000000000000000000000000000000001" - value := "1000000000000000000" + // Ensure instance synced and create alias + _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) + require.NoError(t, err) - _, _ = erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) + // Create an alias for this instance + err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` + USE erc20 { + chain: '%s', + escrow: '%s' + } AS injection_test_bridge + `, chain, escrow), nil, nil) + require.NoError(t, err) - // Inject a transfer: from user to escrow (lock/credit path) - err := testerc20.InjectERC20Transfer(ctx, app, chain, escrow, erc20, user, escrow, value, 1, nil) - require.NoError(t, err) + // Inject a transfer: from user to escrow (lock/credit path) + err = testerc20.InjectERC20Transfer(ctx, app, chain, escrow, erc20, user, escrow, value, 1, nil) + require.NoError(t, err) - // Query balance via action seeded in simple_mock.sql - txCtx := &common.TxContext{ - Ctx: ctx, - BlockContext: &common.BlockContext{Height: 2}, - Signer: platform.Deployer, - Caller: "0x0000000000000000000000000000000000000000", - TxID: platform.Txid(), - } - engCtx := &common.EngineContext{TxContext: txCtx} + // Query balance via the test alias + txCtx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: 2}, + Signer: platform.Deployer, + Caller: "0x0000000000000000000000000000000000000000", + TxID: platform.Txid(), + } + engCtx := &common.EngineContext{TxContext: txCtx} - var got string - r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{user}, func(row *common.Row) error { - if len(row.Values) != 1 { - return fmt.Errorf("expected 1 column, got %d", len(row.Values)) - } - got = fmt.Sprintf("%v", row.Values[0]) - return nil - }) - require.NoError(t, err) - if r != nil && r.Error != nil { - return r.Error - } + var got string + r, err := platform.Engine.Call(engCtx, platform.DB, "injection_test_bridge", "balance", []any{user}, func(row *common.Row) error { + if len(row.Values) != 1 { + return fmt.Errorf("expected 1 column, got %d", len(row.Values)) + } + got = fmt.Sprintf("%v", row.Values[0]) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } - require.Equal(t, value, got, "expected balance to reflect injected transfer amount") - return nil - }, - }, - }, &testutils.Options{Options: testutils.GetTestOptions()}) + require.Equal(t, value, got, "expected balance to reflect injected transfer amount") + + // Cleanup: Deactivate the test instance + testerc20.DeactivateCurrentInstanceTx(t, platform, escrow) + + return nil + }) } diff --git a/tests/extensions/erc20/erc20_bridge_multi_instance_test.go b/tests/extensions/erc20/erc20_bridge_multi_instance_test.go new file mode 100644 index 000000000..9b281b235 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_multi_instance_test.go @@ -0,0 +1,110 @@ +package tests + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + + "github.com/trufnetwork/kwil-db/core/types" + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" +) + +// TestERC20BridgeMultiInstanceIsolation is a minimal multi-instance test. +// Goal: prove balances are isolated across two escrows/aliases. +// Scope: do not test cross-instance internals, only observable separation. +// +// Test flow: +// 1) Create two separate ERC20 instances with different escrow addresses. +// 2) Use direct SQL to create two aliases in the test database. +// 3) Inject deposit only for escrowA, userX. +// 4) Assert aliasA.balance(userX) > 0 and aliasB.balance(userX) == 0. +func TestERC20BridgeMultiInstanceIsolation(t *testing.T) { + seedAndRun(t, "erc20_bridge_multi_instance_isolation", "", func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + + // Use distinct escrow addresses to avoid conflicts with seed files and other tests + escrowA := "0xffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + escrowB := "0xffbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + + // Initialize first instance (escrowA) + _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, escrowA, TestERC20, 18) + require.NoError(t, err) + + // Initialize second instance (escrowB) - same erc20 but different escrow + _, err = erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, escrowB, TestERC20, 18) + require.NoError(t, err) + + // Re-initialize extension to load the new instances into singleton + err = erc20shim.ForTestingInitializeExtension(ctx, app) + require.NoError(t, err) + + // Create aliasA for escrowA + err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` + USE erc20 { + chain: '%s', + escrow: '%s' + } AS aliasA + `, TestChain, escrowA), nil, nil) + require.NoError(t, err) + + // Create aliasB for escrowB + err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` + USE erc20 { + chain: '%s', + escrow: '%s' + } AS aliasB + `, TestChain, escrowB), nil, nil) + require.NoError(t, err) + + // Step 1: Inject deposit only for escrowA, userX + // This simulates a deposit to escrowA only + err = testerc20.InjectERC20Transfer(ctx, app, TestChain, escrowA, TestERC20, TestUserA, escrowA, TestAmount1, 10, nil) + require.NoError(t, err) + + // Step 2: Verify isolation - aliasA should have balance, aliasB should not + engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 2, false) + + // Check balance in aliasA (should have deposit) + var balanceA *types.Decimal + r, err := platform.Engine.Call(engineCtx, platform.DB, "aliasA", "balance", []any{TestUserA}, func(row *common.Row) error { + if len(row.Values) != 1 { + return nil + } + balanceA, _ = row.Values[0].(*types.Decimal) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + require.Equal(t, TestAmount1, balanceA.String(), "aliasA should have the deposit amount") + + // Check balance in aliasB (should be zero - no deposit made) + var balanceB *types.Decimal + r, err = platform.Engine.Call(engineCtx, platform.DB, "aliasB", "balance", []any{TestUserA}, func(row *common.Row) error { + if len(row.Values) != 1 { + return nil + } + balanceB, _ = row.Values[0].(*types.Decimal) + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + require.Equal(t, "0", balanceB.String(), "aliasB should have zero balance (no deposit made)") + + // Cleanup: Deactivate both instances for test isolation + testerc20.DeactivateCurrentInstanceTx(t, platform, escrowA) + testerc20.DeactivateCurrentInstanceTx(t, platform, escrowB) + + return nil + }) +} diff --git a/tests/extensions/erc20/erc20_bridge_transfer_test.go b/tests/extensions/erc20/erc20_bridge_transfer_test.go new file mode 100644 index 000000000..9b5cee839 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_transfer_test.go @@ -0,0 +1,76 @@ +package tests + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + "github.com/trufnetwork/kwil-db/core/types" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" +) + +// TestERC20BridgeTransferBalances is a minimal transfer flow test. +// Goal: verify node can credit a user via injected transfer, then transfer to another user and observe balances. +// Scope: avoid re-testing Kwil internals; assert only the observable integration points. +// +// Test flow: +// 1) Seed with simple_mock.sql and run schema test wrapper. +// 2) Initialize ERC20 test instance via shims. +// 3) Inject deposit: from userA -> escrow; assert alias.balance(userA) == injected. +// 4) Call alias.transfer(to=userB, amount=injected/2); assert balances for userA, userB updated accordingly. +func TestERC20BridgeTransferBalances(t *testing.T) { + seedAndRun(t, "erc20_bridge_transfer_balances", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + + // Singleton reset and initialize is handled by seedAndRun + // The seeded instance may have been deactivated, so we need to reactivate it + _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, TestEscrowA, TestERC20, 18) + require.NoError(t, err) + + err = erc20shim.ForTestingInitializeExtension(ctx, app) + require.NoError(t, err) + + // Step 1: Inject deposit for userA + err = testerc20.InjectERC20Transfer(ctx, app, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount2, 10, nil) + require.NoError(t, err) + + // Verify userA received the full deposit + balanceA, err := testerc20.GetUserBalance(ctx, platform, TestUserA) + require.NoError(t, err) + require.Equal(t, TestAmount2, balanceA, "userA should have full deposit amount") + + // Verify userB has zero balance initially + balanceB, err := testerc20.GetUserBalance(ctx, platform, TestUserB) + require.NoError(t, err) + require.Equal(t, "0", balanceB, "userB should have zero balance initially") + + // Step 2: Transfer half amount from userA to userB + engineCtx := engCtx(ctx, platform, TestUserA, 2, false) + + // transfer expects amount as numeric(78,0) + halfDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) + require.NoError(t, err) + + _, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "transfer", []any{TestUserB, halfDec}, func(row *common.Row) error { + return nil + }) + require.NoError(t, err) + + // Step 3: Verify balances after transfer + // userA should have remaining amount + balanceA, err = testerc20.GetUserBalance(ctx, platform, TestUserA) + require.NoError(t, err) + require.Equal(t, TestAmount1, balanceA, "userA should have remaining amount after transfer") + + // userB should have received the transferred amount + balanceB, err = testerc20.GetUserBalance(ctx, platform, TestUserB) + require.NoError(t, err) + require.Equal(t, TestAmount1, balanceB, "userB should have received transferred amount") + + return nil + }) +} diff --git a/tests/streams/utils/runner.go b/tests/streams/utils/runner.go index cb92c5413..3fc901762 100644 --- a/tests/streams/utils/runner.go +++ b/tests/streams/utils/runner.go @@ -14,6 +14,8 @@ import ( "github.com/trufnetwork/node/tests/streams/utils/cache" "github.com/trufnetwork/node/tests/streams/utils/erc20" "github.com/trufnetwork/node/tests/streams/utils/service" + + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" ) // Options extends kwilTesting.Options with both cache and ERC-20 bridge configuration @@ -69,6 +71,10 @@ func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { wrappedTests = wrapWithExtensionsSetup(context.Background(), s.FunctionTests, cacheConfig, erc20Config, kwilOpts) } + // Ensure ERC20 prepare (USE) is idempotent during seeding to avoid cross-test conflicts + erc20shim.ForTestingAllowPrepareOnActive(true) + defer erc20shim.ForTestingAllowPrepareOnActive(false) + // Run with wrapper kwilTesting.RunSchemaTest(testT, kwilTesting.SchemaTest{ Name: s.Name, From d59bef357ed6d7b5a7dbdc8058188f6ff8a894e9 Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Thu, 11 Sep 2025 14:58:36 -0300 Subject: [PATCH 07/18] feat(tests): enhance ERC-20 bridge tests with new consecutive test cases and cleanup improvements This commit introduces a new test file, `erc20_bridge_consecutive_test.go`, which ensures that running multiple ERC-20 bridge tests consecutively does not result in "already active" errors due to improper cleanup. Key changes include: - Implementation of `TestERC20BridgeConsecutiveTests` to validate the proper handling of multiple tests in sequence. - Refactoring of existing test functions to utilize the new `ForTestingActivateAndInitialize` helper for better instance management. - Improvements to cleanup mechanisms in the test setup to ensure a clean slate before each test run. These changes aim to improve the reliability and robustness of the ERC-20 bridge testing framework, enhancing overall test coverage and maintainability. --- tests/extensions/erc20/README.md | 8 ++ tests/extensions/erc20/common_test.go | 89 +++++++++--------- .../erc20/erc20_bridge_actions_smoke_test.go | 9 +- .../erc20/erc20_bridge_admin_authz_test.go | 2 + .../erc20/erc20_bridge_consecutive_test.go | 90 +++++++++++++++++++ .../erc20/erc20_bridge_end_to_end_test.go | 87 +++++++++++++----- .../erc20/erc20_bridge_epoch_test.go | 19 ++-- .../erc20/erc20_bridge_injection_test.go | 5 ++ .../erc20/erc20_bridge_multi_instance_test.go | 14 ++- .../erc20/erc20_bridge_simple_test.go | 2 + .../erc20/erc20_bridge_transfer_test.go | 2 + tests/streams/utils/erc20_bridge_test.go | 60 ------------- tests/streams/utils/example_test.go | 79 ---------------- tests/streams/utils/runner.go | 7 +- 14 files changed, 241 insertions(+), 232 deletions(-) create mode 100644 tests/extensions/erc20/README.md create mode 100644 tests/extensions/erc20/erc20_bridge_consecutive_test.go delete mode 100644 tests/streams/utils/erc20_bridge_test.go delete mode 100644 tests/streams/utils/example_test.go diff --git a/tests/extensions/erc20/README.md b/tests/extensions/erc20/README.md new file mode 100644 index 000000000..60c3c762a --- /dev/null +++ b/tests/extensions/erc20/README.md @@ -0,0 +1,8 @@ +Notice: to run tests in this directory, you need to build the node with the `kwiltest` tag. +To make it easier on your IDE, you might want to include in the configurations, for example, for VSCode, you can add the following to your `settings.json`: + +```json +{ + "go.testTags": "kwiltest" +} +``` \ No newline at end of file diff --git a/tests/extensions/erc20/common_test.go b/tests/extensions/erc20/common_test.go index b9c4da4f1..b39146210 100644 --- a/tests/extensions/erc20/common_test.go +++ b/tests/extensions/erc20/common_test.go @@ -1,3 +1,5 @@ +//go:build kwiltest + package tests import ( @@ -14,6 +16,7 @@ import ( testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" + evmsync "github.com/trufnetwork/kwil-db/node/exts/evm-sync" ) // Common deterministic values used across ERC20 bridge tests @@ -40,21 +43,23 @@ func seedAndRun(t TestingT, name string, extraSeed string, fn kwilTesting.TestFu // Wrap the test function to add singleton reset and cleanup wrappedFn := func(ctx context.Context, platform *kwilTesting.Platform) error { - // For tests that DO NOT use simple_mock.sql, allow deactivation to ensure clean slate - if extraSeed != "simple_mock.sql" { - if err := deactivateSeededInstance(ctx, platform, extraSeed); err != nil { - return fmt.Errorf("failed to deactivate seeded instance: %w", err) - } + // STEP 1: Reset singleton and prepare for seeding + if err := ensureSingletonReset(ctx, platform); err != nil { + return err } - // Enable idempotent prepare for active instances during test - erc20shim.ForTestingAllowPrepareOnActive(true) - t.Cleanup(func() { erc20shim.ForTestingAllowPrepareOnActive(false) }) + // STEP 2: If using simple_mock.sql, ensure the seeded instance is set up + if extraSeed == "simple_mock.sql" { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + if _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, TestEscrowA, TestERC20, 18); err == nil { + _ = erc20shim.ForTestingInitializeExtension(ctx, app) + } + } - // Add cleanup for seeded instances + // STEP 3: Register cleanup (runs after transaction rollback) addSeedCleanup(t, platform, extraSeed) - // Run the actual test inside a transaction to ensure rollback isolation + // STEP 4: Run the actual test inside a transaction for rollback isolation tx, err := platform.DB.BeginTx(ctx) if err != nil { return fmt.Errorf("begin tx: %w", err) @@ -71,6 +76,9 @@ func seedAndRun(t TestingT, name string, extraSeed string, fn kwilTesting.TestFu return fn(ctx, txPlatform) } + // Ensure the ERC20 test singleton is clean BEFORE seeds run so prepare() uses a fresh cache + erc20shim.ForTestingResetSingleton() + testutils.RunSchemaTest(t.(*testing.T), kwilTesting.SchemaTest{ Name: name, SeedScripts: seedScripts, @@ -116,50 +124,47 @@ func ensureSingletonReset(ctx context.Context, platform *kwilTesting.Platform) e } // addSeedCleanup adds cleanup to deactivate instances used in seeds +// +// This function is called after the test transaction has been rolled back, so it MUST +// use mechanisms that do not rely on a still-open DB connection to tear down listeners/pollers. +// We explicitly unregister pollers/listeners using deterministic IDs, then call the +// transactional cleanup helper as a best-effort DB cleanup. func addSeedCleanup(t TestingT, platform *kwilTesting.Platform, extraSeed string) { // If using simple_mock.sql, add cleanup to deactivate the seeded instance if extraSeed == "simple_mock.sql" { t.Cleanup(func() { + // Unregister poller and listener by unique names (does not require DB) + id := erc20shim.ForTestingGetInstanceID(TestChain, TestEscrowA) + // state poller unique name format: "erc20_state_poll_" + id + pollerName := "erc20_state_poll_" + id.String() + _ = evmsync.StatePoller.UnregisterPoll(pollerName) + // transfer listener unique name available via shim + transferName := erc20shim.ForTestingTransferListenerTopic(*id) + _ = evmsync.EventSyncer.UnregisterListener(transferName) + + // Best-effort DB cleanup (may log a warning if conn closed) testerc20.DeactivateCurrentInstanceTx(t.(*testing.T), platform, TestEscrowA) }) } } // deactivateSeededInstance deactivates the instance created by simple_mock.sql if it exists +// +// This function is called BEFORE the test transaction starts to ensure a clean slate. +// It prevents "already active" errors when running multiple ERC20 tests consecutively. +// +// For simple_mock.sql seeds, it uses the outer (non-transactional) platform DB to ensure +// deactivation persists and resets the singleton to reflect the DB state. +// +// This is different from cleanup which happens AFTER the transaction is rolled back. func deactivateSeededInstance(ctx context.Context, platform *kwilTesting.Platform, extraSeed string) error { if extraSeed == "simple_mock.sql" { - // Try to deactivate the seeded instance to allow reuse - app := &common.App{DB: platform.DB, Engine: platform.Engine} - id, err := erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, TestEscrowA, TestERC20, 18) - if err != nil { - // Instance might not exist or might already be deactivated, ignore error - return nil - } - - // Call the meta extension's disable method - txCtx := &common.TxContext{ - Ctx: ctx, - BlockContext: &common.BlockContext{Height: 1}, - Signer: platform.Deployer, - Caller: "0x0000000000000000000000000000000000000000", - TxID: platform.Txid(), - } - engCtx := &common.EngineContext{TxContext: txCtx, OverrideAuthz: true} - - _, err = platform.Engine.Call(engCtx, platform.DB, "kwil_erc20_meta", "disable", []any{id}, func(row *common.Row) error { - return nil - }) - if err != nil { - // Instance might already be deactivated, ignore error - return nil - } - - // Reset and re-initialize the singleton to reflect the deactivated state - erc20shim.ForTestingResetSingleton() - err = erc20shim.ForTestingInitializeExtension(ctx, app) - if err != nil { - return fmt.Errorf("failed to re-initialize singleton: %w", err) - } + // Proactively unregister any previously registered poller/listener (no DB required) + id := erc20shim.ForTestingGetInstanceID(TestChain, TestEscrowA) + pollerName := "erc20_state_poll_" + id.String() + _ = evmsync.StatePoller.UnregisterPoll(pollerName) + transferName := erc20shim.ForTestingTransferListenerTopic(*id) + _ = evmsync.EventSyncer.UnregisterListener(transferName) } return nil } diff --git a/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go index 1396e5c17..ae0790b46 100644 --- a/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go +++ b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go @@ -1,3 +1,5 @@ +//go:build kwiltest + package tests import ( @@ -27,11 +29,8 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { app := &common.App{DB: platform.DB, Engine: platform.Engine} // Singleton reset and initialize is handled by seedAndRun - // The seeded instance may have been deactivated, so we need to reactivate it - _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, TestEscrowA, TestERC20, 18) - require.NoError(t, err) - - err = erc20shim.ForTestingInitializeExtension(ctx, app) + // Ensure active+synced in-memory + err := erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, TestEscrowA, TestERC20, 18, 60) require.NoError(t, err) // Test erc20.info() action diff --git a/tests/extensions/erc20/erc20_bridge_admin_authz_test.go b/tests/extensions/erc20/erc20_bridge_admin_authz_test.go index c546b7934..9985468f3 100644 --- a/tests/extensions/erc20/erc20_bridge_admin_authz_test.go +++ b/tests/extensions/erc20/erc20_bridge_admin_authz_test.go @@ -1,3 +1,5 @@ +//go:build kwiltest + package tests import ( diff --git a/tests/extensions/erc20/erc20_bridge_consecutive_test.go b/tests/extensions/erc20/erc20_bridge_consecutive_test.go new file mode 100644 index 000000000..1d65cdc22 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_consecutive_test.go @@ -0,0 +1,90 @@ +//go:build kwiltest + +package tests + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" +) + +// TestERC20BridgeConsecutiveTests ensures that running multiple ERC20 bridge tests +// consecutively does not fail with "already active" errors due to improper cleanup. +func TestERC20BridgeConsecutiveTests(t *testing.T) { + // Test 1: Run first ERC20 test + t.Run("first_test", func(t *testing.T) { + seedAndRun(t, "erc20_bridge_first", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + + // Ensure the seeded alias instance exists, active and synced + err := erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, TestEscrowA, TestERC20, 18, 60) + require.NoError(t, err) + + // Simple test: just call info() to verify the extension is working + engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 1, false) + var syncedResult bool + res, err := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "info", []any{}, func(row *common.Row) error { + if len(row.Values) >= 7 { + syncedResult = row.Values[6].(bool) + } + return nil + }) + require.NoError(t, err) + if res != nil && res.Error != nil { + return res.Error + } + require.True(t, syncedResult, "first test: instance should be synced") + + return nil + }) + }) + + // Test 2: Run second ERC20 test immediately after the first + // This test will fail with "already active" if our cleanup doesn't work + t.Run("second_test", func(t *testing.T) { + seedAndRun(t, "erc20_bridge_second", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { + app := &common.App{DB: platform.DB, Engine: platform.Engine} + + // This should not fail with "already active" error due to proper cleanup + err := erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, TestEscrowA, TestERC20, 18, 60) + if err != nil { + // If this fails, let's check the DB state to debug + var isActive bool + checkErr := app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, ` + {kwil_erc20_meta}SELECT active FROM reward_instances WHERE id = $id + `, map[string]any{"id": erc20shim.ForTestingGetInstanceID(TestChain, TestEscrowA)}, func(row *common.Row) error { + if len(row.Values) == 1 { + isActive = row.Values[0].(bool) + } + return nil + }) + if checkErr == nil { + t.Logf("DEBUG: Instance active in DB: %v", isActive) + } + require.NoError(t, err, "second test: should not fail with 'already active' error") + } + + // Simple test: just call info() to verify the extension is working + engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 1, false) + var syncedResult bool + res, err := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "info", []any{}, func(row *common.Row) error { + if len(row.Values) >= 7 { + syncedResult = row.Values[6].(bool) + } + return nil + }) + require.NoError(t, err) + if res != nil && res.Error != nil { + return res.Error + } + require.True(t, syncedResult, "second test: instance should be synced") + + return nil + }) + }) +} diff --git a/tests/extensions/erc20/erc20_bridge_end_to_end_test.go b/tests/extensions/erc20/erc20_bridge_end_to_end_test.go index 2c530a5aa..ae1116d91 100644 --- a/tests/extensions/erc20/erc20_bridge_end_to_end_test.go +++ b/tests/extensions/erc20/erc20_bridge_end_to_end_test.go @@ -1,9 +1,12 @@ +//go:build kwiltest + package tests import ( "context" "testing" + ethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/trufnetwork/kwil-db/common" kwilTesting "github.com/trufnetwork/kwil-db/testing" @@ -11,7 +14,6 @@ import ( "github.com/trufnetwork/kwil-db/core/types" erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" - deverc20 "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20/dev" ) // TestERC20BridgeEndToEnd is a minimal E2E test using only actions + shims. @@ -29,17 +31,29 @@ func TestERC20BridgeEndToEnd(t *testing.T) { seedAndRun(t, "erc20_bridge_end_to_end", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { app := &common.App{DB: platform.DB, Engine: platform.Engine} - // Singleton reset and initialize is handled by seedAndRun - // Use the sepolia_bridge alias created by simple_mock.sql - - // Set small distribution period for quick epoch progression using dev helper - require.NoError(t, deverc20.SetDistributionPeriod(ctx, app, TestChain, TestEscrowA, 1)) - - err := erc20shim.ForTestingInitializeExtension(ctx, app) - require.NoError(t, err) + // Ensure the seeded alias instance exists, active and synced in DB and singleton + require.NoError(t, erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, TestEscrowA, TestERC20, 18, 1)) + + // Sanity: ensure the instance reports synced and enabled via info() + engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 1, false) + var syncedResult, enabledResult bool + resInfo, errInfo := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "info", []any{}, func(row *common.Row) error { + if len(row.Values) < 9 { + return nil + } + syncedResult = row.Values[6].(bool) + enabledResult = row.Values[8].(bool) + return nil + }) + require.NoError(t, errInfo) + if resInfo != nil && resInfo.Error != nil { + return resInfo.Error + } + require.True(t, syncedResult, "instance should be synced before bridge") + require.True(t, enabledResult, "instance should be enabled before bridge") // Step 1: Inject deposit to give user a balance - err = testerc20.InjectERC20Transfer(ctx, app, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount1, 10, nil) + err := testerc20.InjectERC20Transfer(ctx, app, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount1, 10, nil) require.NoError(t, err) // Verify user has the balance @@ -48,27 +62,60 @@ func TestERC20BridgeEndToEnd(t *testing.T) { require.Equal(t, TestAmount1, balance, "user should have deposit amount") // Step 2: Call bridge action as user to lock+issue into epoch - engineCtx := engCtx(ctx, platform, TestUserA, 2, false) + engineCtx = engCtx(ctx, platform, TestUserA, 2, false) amtDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) require.NoError(t, err) - _, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "bridge", []any{amtDec}, func(row *common.Row) error { + r, err := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "bridge", []any{amtDec}, func(row *common.Row) error { return nil }) require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } - // Step 3: Finalize current epoch and create next - var bh [32]byte - require.NoError(t, erc20shim.ForTestingFinalizeCurrentEpoch(ctx, app, TestChain, TestEscrowA, 11, bh)) + // Verify a pending reward exists after bridge for this instance and user + preQ := ` + {kwil_erc20_meta}SELECT count(*) FROM epoch_rewards r + JOIN epochs e ON e.id = r.epoch_id + WHERE e.instance_id = $id AND r.recipient = $user` + var preRows int + err = platform.Engine.ExecuteWithoutEngineCtx(ctx, platform.DB, preQ, map[string]any{ + "id": erc20shim.ForTestingGetInstanceID(TestChain, TestEscrowA), + "user": ethcommon.HexToAddress(TestUserA).Bytes(), + }, func(row *common.Row) error { + if len(row.Values) != 1 { + return nil + } + preRows = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err) + require.Greater(t, preRows, 0, "expected pending epoch reward before finalize for user in this instance") - // Step 4: Confirm finalized epochs - require.NoError(t, erc20shim.ForTestingConfirmAllFinalizedEpochs(ctx, app, TestChain, TestEscrowA)) + // Step 3-4: Deterministically finalize current epoch and confirm + var bh [32]byte + require.NoError(t, erc20shim.ForTestingFinalizeAndConfirmCurrentEpoch(ctx, app, TestChain, TestEscrowA, 11, bh)) - // Step 5: Query wallet rewards to verify bridge flow worked + // Diagnostics: fetch instance id via alias engineCtx = engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 3, false) + var instanceID *types.UUID + resID, errID := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "id", []any{}, func(row *common.Row) error { + if len(row.Values) != 1 { + return nil + } + instanceID = row.Values[0].(*types.UUID) + return nil + }) + require.NoError(t, errID) + if resID != nil && resID.Error != nil { + return resID.Error + } + require.NotNil(t, instanceID, "instance id should not be nil") + // Step 5: Query wallet rewards (confirmed only) to verify bridge flow worked deterministically rewardRows := 0 - r, err := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "list_wallet_rewards", []any{TestUserA, false}, func(row *common.Row) error { + r, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "list_wallet_rewards", []any{TestUserA, false}, func(row *common.Row) error { rewardRows++ return nil }) @@ -76,8 +123,6 @@ func TestERC20BridgeEndToEnd(t *testing.T) { if r != nil && r.Error != nil { return r.Error } - - // Assert that at least one reward row was returned require.Greater(t, rewardRows, 0, "user should have at least one wallet reward after bridge flow") return nil diff --git a/tests/extensions/erc20/erc20_bridge_epoch_test.go b/tests/extensions/erc20/erc20_bridge_epoch_test.go index 738d8c9ad..f18222740 100644 --- a/tests/extensions/erc20/erc20_bridge_epoch_test.go +++ b/tests/extensions/erc20/erc20_bridge_epoch_test.go @@ -1,3 +1,5 @@ +//go:build kwiltest + package tests import ( @@ -29,15 +31,11 @@ func TestERC20BridgeEpochFlow(t *testing.T) { user := "0xabc0000000000000000000000000000000000001" value := "500000000000000000" // 0.5 - // Ensure instance synced (register topic and create DB instance/epoch if missing) - _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) - require.NoError(t, err) - - // Make distribution period small for test determinism - require.NoError(t, erc20shim.ForTestingSetDistributionPeriod(ctx, app, chain, escrow, 1)) + // Ensure active+synced in-memory using the new helper + require.NoError(t, erc20shim.ForTestingActivateAndInitialize(ctx, app, chain, escrow, erc20, 18, 1)) // Create an alias for this instance - err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` + err := app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` USE erc20 { chain: '%s', escrow: '%s' @@ -65,12 +63,9 @@ func TestERC20BridgeEpochFlow(t *testing.T) { require.NoError(t, err) require.Greater(t, preRows, 0, "expected pending epoch reward before finalize") - // Finalize current epoch and create next + // Finalize and confirm via helper var bh [32]byte - require.NoError(t, erc20shim.ForTestingFinalizeCurrentEpoch(ctx, app, chain, escrow, 11, bh)) - - // Confirm finalized epochs - require.NoError(t, erc20shim.ForTestingConfirmAllFinalizedEpochs(ctx, app, chain, escrow)) + require.NoError(t, erc20shim.ForTestingFinalizeAndConfirmCurrentEpoch(ctx, app, chain, escrow, 11, bh)) // Query confirmed rewards directly q := ` diff --git a/tests/extensions/erc20/erc20_bridge_injection_test.go b/tests/extensions/erc20/erc20_bridge_injection_test.go index ca6fd9b1f..1d4772fca 100644 --- a/tests/extensions/erc20/erc20_bridge_injection_test.go +++ b/tests/extensions/erc20/erc20_bridge_injection_test.go @@ -1,3 +1,5 @@ +//go:build kwiltest + package tests import ( @@ -29,6 +31,9 @@ func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) require.NoError(t, err) + // Load instances into singleton to avoid duplicate prepare on USE + require.NoError(t, erc20shim.ForTestingInitializeExtension(ctx, app)) + // Create an alias for this instance err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` USE erc20 { diff --git a/tests/extensions/erc20/erc20_bridge_multi_instance_test.go b/tests/extensions/erc20/erc20_bridge_multi_instance_test.go index 9b281b235..23c647541 100644 --- a/tests/extensions/erc20/erc20_bridge_multi_instance_test.go +++ b/tests/extensions/erc20/erc20_bridge_multi_instance_test.go @@ -1,3 +1,5 @@ +//go:build kwiltest + package tests import ( @@ -31,16 +33,10 @@ func TestERC20BridgeMultiInstanceIsolation(t *testing.T) { escrowA := "0xffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" escrowB := "0xffbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" - // Initialize first instance (escrowA) - _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, escrowA, TestERC20, 18) - require.NoError(t, err) - - // Initialize second instance (escrowB) - same erc20 but different escrow - _, err = erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, escrowB, TestERC20, 18) + // Initialize both instances active+synced + err := erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, escrowA, TestERC20, 18, 60) require.NoError(t, err) - - // Re-initialize extension to load the new instances into singleton - err = erc20shim.ForTestingInitializeExtension(ctx, app) + err = erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, escrowB, TestERC20, 18, 60) require.NoError(t, err) // Create aliasA for escrowA diff --git a/tests/extensions/erc20/erc20_bridge_simple_test.go b/tests/extensions/erc20/erc20_bridge_simple_test.go index c64b6e076..0b641de60 100644 --- a/tests/extensions/erc20/erc20_bridge_simple_test.go +++ b/tests/extensions/erc20/erc20_bridge_simple_test.go @@ -1,3 +1,5 @@ +//go:build kwiltest + package tests import ( diff --git a/tests/extensions/erc20/erc20_bridge_transfer_test.go b/tests/extensions/erc20/erc20_bridge_transfer_test.go index 9b5cee839..60ca2caf2 100644 --- a/tests/extensions/erc20/erc20_bridge_transfer_test.go +++ b/tests/extensions/erc20/erc20_bridge_transfer_test.go @@ -1,3 +1,5 @@ +//go:build kwiltest + package tests import ( diff --git a/tests/streams/utils/erc20_bridge_test.go b/tests/streams/utils/erc20_bridge_test.go deleted file mode 100644 index 8ee2c02d4..000000000 --- a/tests/streams/utils/erc20_bridge_test.go +++ /dev/null @@ -1,60 +0,0 @@ -// Package testutils provides ERC-20 bridge testing utilities. -// -// This file contains test-specific utilities for ERC-20 bridge testing. -// All core functionality is implemented in utils.go for better organization. -// -// Key features: -// - Mock ERC-20 event generation -// - Test-specific listener implementations -// - ERC-20 bridge testing helpers -package testutils - -import ( - "context" - "fmt" - - "github.com/trufnetwork/kwil-db/common" - "github.com/trufnetwork/kwil-db/extensions/listeners" - "github.com/trufnetwork/node/tests/streams/utils/erc20" -) - -// Note: Core mock functions are defined in utils.go for better organization -// These functions are available via the testutils package - -// MockERC20DepositListener creates a mock listener that simulates deposit events -func MockERC20DepositListener(userAddress, tokenAddress, amount string) listeners.ListenFunc { - return func(ctx context.Context, service *common.Service, eventstore listeners.EventStore) error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - mockData := erc20.CreateMockERC20Deposit(userAddress, tokenAddress, amount) - if err := eventstore.Broadcast(ctx, "erc20_deposit", mockData); err != nil { - return fmt.Errorf("failed to broadcast deposit event: %w", err) - } - } - - // Wait for context cancellation - <-ctx.Done() - return ctx.Err() - } -} - -// MockERC20WithdrawalListener creates a mock listener that simulates withdrawal events -func MockERC20WithdrawalListener(userAddress, tokenAddress, amount string) listeners.ListenFunc { - return func(ctx context.Context, service *common.Service, eventstore listeners.EventStore) error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - mockData := erc20.CreateMockERC20Withdrawal(userAddress, tokenAddress, amount) - if err := eventstore.Broadcast(ctx, "erc20_withdrawal", mockData); err != nil { - return fmt.Errorf("failed to broadcast withdrawal event: %w", err) - } - } - - // Wait for context cancellation - <-ctx.Done() - return ctx.Err() - } -} diff --git a/tests/streams/utils/example_test.go b/tests/streams/utils/example_test.go deleted file mode 100644 index 7d38e6821..000000000 --- a/tests/streams/utils/example_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package testutils - -import ( - "testing" - - kwilTesting "github.com/trufnetwork/kwil-db/testing" -) - -func TestExampleERC20BridgeTesting(t *testing.T) { - // Example of how to use the reorganized ERC-20 bridge testing utilities - - // Setup ERC-20 bridge configuration - erc20Config := NewERC20BridgeConfig(). - WithRPC("sepolia", "wss://your-sepolia-provider.com"). - WithSigner("test_bridge", "/path/to/signer/key"). - WithAutoStart() - - // Create test options - opts := GetTestOptionsWithERC20Bridge(erc20Config) - - // Define your schema test - schemaTest := kwilTesting.SchemaTest{ - Name: "ERC-20 Bridge Test", - // ... your test cases - } - - // Note: In a real test, you would call RunSchemaTest(t, schemaTest, opts) - // For this example, we just verify the configuration is valid - _ = schemaTest - _ = opts -} - -func TestExampleMockERC20BridgeTesting(t *testing.T) { - // Example of mock testing - mockListener := MockERC20Listener([]string{"erc20_transfer"}) - erc20Config := MockERC20Bridge(mockListener) - opts := GetTestOptionsWithERC20Bridge(erc20Config) - - // Use in tests - _ = opts -} - -func TestExampleCombinedCacheAndERC20Testing(t *testing.T) { - // Example of using both cache and ERC-20 bridge - cacheOpts := SimpleCache("0x123...", "stream1") - erc20Opts := SimpleERC20Bridge("wss://rpc.com", "/key/path") - opts := GetTestOptionsWithBoth(cacheOpts, erc20Opts) - - // Use in tests - _ = opts -} - -func TestReorganizationWorks(t *testing.T) { - // This test verifies that the reorganized utilities compile and work - - // Test cache options - cacheOpts := NewCacheOptions(). - WithEnabled(). - WithStream("test_provider", "test_stream", "0 * * * *") - - if !cacheOpts.IsEnabled() { - t.Error("Cache should be enabled") - } - - // Test ERC-20 bridge options - erc20Opts := NewERC20BridgeConfig(). - WithRPC("test_chain", "wss://test-rpc.com"). - WithSigner("test_signer", "/test/key") - - if erc20Opts.RPC["test_chain"] != "wss://test-rpc.com" { - t.Error("RPC URL should be set") - } - - // Test combined options - combinedOpts := GetTestOptionsWithBoth(cacheOpts, erc20Opts) - if combinedOpts.Cache == nil || combinedOpts.ERC20Bridge == nil { - t.Error("Combined options should have both cache and ERC-20 bridge") - } -} diff --git a/tests/streams/utils/runner.go b/tests/streams/utils/runner.go index 3fc901762..b770981b8 100644 --- a/tests/streams/utils/runner.go +++ b/tests/streams/utils/runner.go @@ -15,7 +15,7 @@ import ( "github.com/trufnetwork/node/tests/streams/utils/erc20" "github.com/trufnetwork/node/tests/streams/utils/service" - erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" + orderedsync "github.com/trufnetwork/kwil-db/node/exts/ordered-sync" ) // Options extends kwilTesting.Options with both cache and ERC-20 bridge configuration @@ -71,9 +71,8 @@ func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { wrappedTests = wrapWithExtensionsSetup(context.Background(), s.FunctionTests, cacheConfig, erc20Config, kwilOpts) } - // Ensure ERC20 prepare (USE) is idempotent during seeding to avoid cross-test conflicts - erc20shim.ForTestingAllowPrepareOnActive(true) - defer erc20shim.ForTestingAllowPrepareOnActive(false) + // Ensure isolation: reset ordered-sync in-memory topics before test run + orderedsync.ForTestingReset() // Run with wrapper kwilTesting.RunSchemaTest(testT, kwilTesting.SchemaTest{ From 434ea776b9adb635f46835b609e79d9869ae58ad Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Thu, 11 Sep 2025 17:04:05 -0300 Subject: [PATCH 08/18] refactor(tests): streamline ERC-20 bridge tests by removing seed script dependencies This commit refactors the ERC-20 bridge test suite to eliminate reliance on seed scripts, enhancing test reliability and maintainability. Key changes include: - Updated test functions to utilize `ForTestingSeedAndActivateInstance` for instance management, ensuring proper setup and teardown. - Removed the `simple_mock.sql` seed script, consolidating instance initialization within the test functions. - Enhanced cleanup mechanisms to ensure a clean state before each test run, preventing "already active" errors. These changes aim to improve the clarity and robustness of the ERC-20 bridge testing framework, facilitating easier maintenance and future enhancements. --- tests/extensions/erc20/README.md | 57 +++++++- tests/extensions/erc20/common_test.go | 101 ++------------ .../erc20/erc20_bridge_actions_smoke_test.go | 17 +-- .../erc20/erc20_bridge_admin_authz_test.go | 21 ++- .../erc20/erc20_bridge_consecutive_test.go | 20 ++- .../erc20/erc20_bridge_end_to_end_test.go | 22 ++-- .../erc20/erc20_bridge_epoch_test.go | 30 ++--- .../erc20/erc20_bridge_injection_test.go | 26 +--- .../erc20/erc20_bridge_multi_instance_test.go | 39 ++---- .../erc20/erc20_bridge_simple_test.go | 123 ++++-------------- .../erc20/erc20_bridge_transfer_test.go | 24 ++-- tests/extensions/erc20/simple_mock.sql | 11 -- tests/streams/utils/erc20/helper.go | 100 ++------------ tests/streams/utils/erc20/inject.go | 9 +- 14 files changed, 172 insertions(+), 428 deletions(-) delete mode 100644 tests/extensions/erc20/simple_mock.sql diff --git a/tests/extensions/erc20/README.md b/tests/extensions/erc20/README.md index 60c3c762a..d65a7db37 100644 --- a/tests/extensions/erc20/README.md +++ b/tests/extensions/erc20/README.md @@ -5,4 +5,59 @@ To make it easier on your IDE, you might want to include in the configurations, { "go.testTags": "kwiltest" } -``` \ No newline at end of file +``` + +### Objective + +This directory demonstrates a minimal, production-faithful structure for wiring and invoking the ERC‑20 bridge extension in tests. It: + +- Shows how to bootstrap an instance, call actions, and cleanly tear down using the `ForTesting*` helpers. +- Demonstrates how tests should evoke the extension (e.g., alias usage, numeric arguments, engine context, authz boundaries) in isolation. +- Serves as a reference pattern to later integrate these flows into our standard streams tests appropriately. + +### Quick start + +- Enable the `kwiltest` tag (above) so the test helpers are available. +- Seed and activate an instance for a test: + +```go +err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, chain, escrow, erc20, 18, 60, alias) +``` + +- Invoke actions using the alias (e.g., `balance`, `bridge`, `lock_admin`). +- For epoch flows, use `ForTestingFinalizeAndConfirmCurrentEpoch(...)` to deterministically advance. +- Cleanup explicitly with `ForTestingDisableInstance(...)` or rely on the test wrapper’s registered cleanup. + +### Why helpers over plain SQL/`USE ...` for ERC-20 tests + +- **The ERC-20 bridge extension has runtime wiring beyond SQL.** In production it creates and manages background components and in‑memory state: ordered‑sync topics/listeners for `Transfer` events, a state poller, a singleton cache rehydrated on start, epoch lifecycle rows, and synced ERC‑20 metadata. A bare `USE erc20 { ... } AS alias` only creates the alias; it does not guarantee these side‑effects are present or initialized in the current process. +- **Plain seeds miss critical initialization.** Relying only on SQL seeds/`USE` won’t ensure the `kwil_erc20_meta` schema exists, the `reward_instances` row and first epoch are created, the instance is marked synced with the ERC‑20 address/decimals, the ordered‑sync topic is registered, or that the extension’s singleton has been rehydrated. That leads to flaky tests and "already active"/missing‑wiring failures. + +### What `ForTestingSeedAndActivateInstance` does + +`ForTestingSeedAndActivateInstance(ctx, platform, chain, escrow, erc20, decimals, periodSeconds, alias)` performs, idempotently: + +- Creates the extension alias via `USE erc20 { chain: '', escrow: '' } AS `. +- Ensures the `kwil_erc20_meta` namespace/schema exist and computes the deterministic instance ID. +- Registers the ordered‑sync transfer topic/state‑poller names for the instance. +- Creates the `reward_instances` row if missing and guarantees a first pending epoch. +- Marks the instance as synced with the provided ERC‑20 address and `decimals`. +- Sets the distribution period to `periodSeconds` for predictable epochs. +- Re‑initializes the extension (OnStart‑like) so the in‑memory singleton reflects DB state. + +This yields a production‑like, deterministic setup inside a test transaction, without depending on external chains. + +### Cleanup: disabling and teardown + +`ForTestingDisableInstance(ctx, platform, chain, escrow, alias)` performs deterministic teardown: + +- Executes `UNUSE ` to drop the alias in the test DB. +- Calls `kwil_erc20_meta.disable(id)` to deactivate the instance in storage. +- Unregisters the state poller and transfer listener for the instance. +- Resets and re‑initializes the singleton to a clean state. + +Additionally, our test wrapper registers cleanup that resets the singleton and clears runtimes to prevent cross‑test contamination. + +### TL;DR + +Use the provided `ForTesting*` helpers instead of plain SQL/`USE` to mirror production wiring that SQL alone cannot trigger, ensuring deterministic, isolated ERC‑20 extension tests. \ No newline at end of file diff --git a/tests/extensions/erc20/common_test.go b/tests/extensions/erc20/common_test.go index b39146210..4d1997781 100644 --- a/tests/extensions/erc20/common_test.go +++ b/tests/extensions/erc20/common_test.go @@ -5,18 +5,14 @@ package tests import ( "context" "fmt" - "path/filepath" - "runtime" "testing" "github.com/trufnetwork/kwil-db/common" kwilTesting "github.com/trufnetwork/kwil-db/testing" "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" - testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" - evmsync "github.com/trufnetwork/kwil-db/node/exts/evm-sync" ) // Common deterministic values used across ERC20 bridge tests @@ -31,35 +27,19 @@ const ( TestAmount2 = "2000000000000000000" // 2.0 tokens ) -// seedAndRun is a helper that handles seed script setup and runs the test with proper isolation -func seedAndRun(t TestingT, name string, extraSeed string, fn kwilTesting.TestFunc) { +// seedAndRun is a helper that handles test execution with proper isolation +func seedAndRun(t TestingT, name string, fn kwilTesting.TestFunc) { seedScripts := migrations.GetSeedScriptPaths() - if extraSeed != "" { - _, thisFile, _, _ := runtime.Caller(1) - extraSeedPath := filepath.Join(filepath.Dir(thisFile), extraSeed) - seedScripts = append(seedScripts, extraSeedPath) - } - // Wrap the test function to add singleton reset and cleanup wrappedFn := func(ctx context.Context, platform *kwilTesting.Platform) error { - // STEP 1: Reset singleton and prepare for seeding - if err := ensureSingletonReset(ctx, platform); err != nil { - return err - } - - // STEP 2: If using simple_mock.sql, ensure the seeded instance is set up - if extraSeed == "simple_mock.sql" { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - if _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, TestEscrowA, TestERC20, 18); err == nil { - _ = erc20shim.ForTestingInitializeExtension(ctx, app) - } - } - - // STEP 3: Register cleanup (runs after transaction rollback) - addSeedCleanup(t, platform, extraSeed) + // STEP 1: Register cleanup (runs after transaction rollback) + t.Cleanup(func() { + erc20shim.ForTestingClearAllInstances(ctx, platform) + erc20shim.ForTestingResetSingleton() + }) - // STEP 4: Run the actual test inside a transaction for rollback isolation + // STEP 2: Run the actual test inside a transaction for rollback isolation tx, err := platform.DB.BeginTx(ctx) if err != nil { return fmt.Errorf("begin tx: %w", err) @@ -76,9 +56,6 @@ func seedAndRun(t TestingT, name string, extraSeed string, fn kwilTesting.TestFu return fn(ctx, txPlatform) } - // Ensure the ERC20 test singleton is clean BEFORE seeds run so prepare() uses a fresh cache - erc20shim.ForTestingResetSingleton() - testutils.RunSchemaTest(t.(*testing.T), kwilTesting.SchemaTest{ Name: name, SeedScripts: seedScripts, @@ -106,65 +83,3 @@ type TestingT interface { Errorf(format string, args ...interface{}) Cleanup(func()) } - -// ensureSingletonReset ensures the ERC20 singleton is reset and initialized for test isolation -func ensureSingletonReset(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - - // Reset singleton to ensure clean state - erc20shim.ForTestingResetSingleton() - - // Initialize extension to load any DB state into singleton - err := erc20shim.ForTestingInitializeExtension(ctx, app) - if err != nil { - return fmt.Errorf("failed to initialize ERC20 extension: %w", err) - } - - return nil -} - -// addSeedCleanup adds cleanup to deactivate instances used in seeds -// -// This function is called after the test transaction has been rolled back, so it MUST -// use mechanisms that do not rely on a still-open DB connection to tear down listeners/pollers. -// We explicitly unregister pollers/listeners using deterministic IDs, then call the -// transactional cleanup helper as a best-effort DB cleanup. -func addSeedCleanup(t TestingT, platform *kwilTesting.Platform, extraSeed string) { - // If using simple_mock.sql, add cleanup to deactivate the seeded instance - if extraSeed == "simple_mock.sql" { - t.Cleanup(func() { - // Unregister poller and listener by unique names (does not require DB) - id := erc20shim.ForTestingGetInstanceID(TestChain, TestEscrowA) - // state poller unique name format: "erc20_state_poll_" + id - pollerName := "erc20_state_poll_" + id.String() - _ = evmsync.StatePoller.UnregisterPoll(pollerName) - // transfer listener unique name available via shim - transferName := erc20shim.ForTestingTransferListenerTopic(*id) - _ = evmsync.EventSyncer.UnregisterListener(transferName) - - // Best-effort DB cleanup (may log a warning if conn closed) - testerc20.DeactivateCurrentInstanceTx(t.(*testing.T), platform, TestEscrowA) - }) - } -} - -// deactivateSeededInstance deactivates the instance created by simple_mock.sql if it exists -// -// This function is called BEFORE the test transaction starts to ensure a clean slate. -// It prevents "already active" errors when running multiple ERC20 tests consecutively. -// -// For simple_mock.sql seeds, it uses the outer (non-transactional) platform DB to ensure -// deactivation persists and resets the singleton to reflect the DB state. -// -// This is different from cleanup which happens AFTER the transaction is rolled back. -func deactivateSeededInstance(ctx context.Context, platform *kwilTesting.Platform, extraSeed string) error { - if extraSeed == "simple_mock.sql" { - // Proactively unregister any previously registered poller/listener (no DB required) - id := erc20shim.ForTestingGetInstanceID(TestChain, TestEscrowA) - pollerName := "erc20_state_poll_" + id.String() - _ = evmsync.StatePoller.UnregisterPoll(pollerName) - transferName := erc20shim.ForTestingTransferListenerTopic(*id) - _ = evmsync.EventSyncer.UnregisterListener(transferName) - } - return nil -} diff --git a/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go index ae0790b46..c19642c10 100644 --- a/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go +++ b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go @@ -25,12 +25,11 @@ import ( // 4) Call erc20.scale_up("1") and expect 1000000000000000000 (1 * 10^18) // 5) Call erc20.scale_down(1e18) and expect "1" func TestERC20BridgeActionsSmoke(t *testing.T) { - seedAndRun(t, "erc20_bridge_actions_smoke", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} + seedAndRun(t, "erc20_bridge_actions_smoke", func(ctx context.Context, platform *kwilTesting.Platform) error { // Singleton reset and initialize is handled by seedAndRun // Ensure active+synced in-memory - err := erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, TestEscrowA, TestERC20, 18, 60) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) require.NoError(t, err) // Test erc20.info() action @@ -38,10 +37,8 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { var chainResult, escrowResult, periodResult string var syncedResult bool - r, err := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "info", []any{}, func(row *common.Row) error { - if len(row.Values) < 4 { - return nil // Skip if not enough fields - } + r, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "info", []any{}, func(row *common.Row) error { + chainResult = row.Values[0].(string) escrowResult = row.Values[1].(string) periodResult = row.Values[2].(string) @@ -61,7 +58,7 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { // Test erc20.decimals() action var decimalsResult int64 - r, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "decimals", []any{}, func(row *common.Row) error { + r, err = platform.Engine.Call(engineCtx, platform.DB, TestChain, "decimals", []any{}, func(row *common.Row) error { if len(row.Values) != 1 { return nil } @@ -77,7 +74,7 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { // Test erc20.scale_up("1") action var scaledUpResult *types.Decimal - r, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "scale_up", []any{"1"}, func(row *common.Row) error { + r, err = platform.Engine.Call(engineCtx, platform.DB, TestChain, "scale_up", []any{"1"}, func(row *common.Row) error { if len(row.Values) != 1 { return nil } @@ -98,7 +95,7 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { amountDecimal, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) require.NoError(t, err) - r, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "scale_down", []any{amountDecimal}, func(row *common.Row) error { + r, err = platform.Engine.Call(engineCtx, platform.DB, TestChain, "scale_down", []any{amountDecimal}, func(row *common.Row) error { if len(row.Values) != 1 { return nil } diff --git a/tests/extensions/erc20/erc20_bridge_admin_authz_test.go b/tests/extensions/erc20/erc20_bridge_admin_authz_test.go index 9985468f3..55bccdc0c 100644 --- a/tests/extensions/erc20/erc20_bridge_admin_authz_test.go +++ b/tests/extensions/erc20/erc20_bridge_admin_authz_test.go @@ -26,20 +26,17 @@ import ( // 3) Attempt lock_admin without OverrideAuthz: expect execution error. // 4) Retry with OverrideAuthz: expect success and balance changes to reflect lock. func TestERC20BridgeAdminAuthz(t *testing.T) { - seedAndRun(t, "erc20_bridge_admin_authz", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - - // Singleton reset and initialize is handled by seedAndRun - // Use the sepolia_bridge alias created by simple_mock.sql - err := erc20shim.ForTestingInitializeExtension(ctx, app) + seedAndRun(t, "erc20_bridge_admin_authz", func(ctx context.Context, platform *kwilTesting.Platform) error { + // Enable instance with alias for admin authz test + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) require.NoError(t, err) // Step 1: Inject deposit so user has balance - err = testerc20.InjectERC20Transfer(ctx, app, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount2, 10, nil) + err = testerc20.InjectERC20Transfer(ctx, platform, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount2, 10, nil) require.NoError(t, err) // Verify user has the balance - balance, err := testerc20.GetUserBalance(ctx, platform, TestUserA) + balance, err := testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount2, balance, "user should have deposit amount") @@ -50,21 +47,21 @@ func TestERC20BridgeAdminAuthz(t *testing.T) { amtDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) require.NoError(t, err) - _, err = platform.Engine.Call(engineCtxFail, platform.DB, "sepolia_bridge", "lock_admin", []any{TestUserA, amtDec}, func(row *common.Row) error { + _, err = platform.Engine.Call(engineCtxFail, platform.DB, TestChain, "lock_admin", []any{TestUserA, amtDec}, func(row *common.Row) error { return nil }) // Should fail with authz error (SYSTEM method called without OverrideAuthz) require.Error(t, err, "action lock_admin is system-only") // Balance should remain unchanged - balance, err = testerc20.GetUserBalance(ctx, platform, TestUserA) + balance, err = testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount2, balance, "balance should remain unchanged after failed lock_admin") // Step 3: Retry lock_admin WITH OverrideAuthz - should succeed engineCtxSuccess := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 3, true) - r, err := platform.Engine.Call(engineCtxSuccess, platform.DB, "sepolia_bridge", "lock_admin", []any{TestUserA, amtDec}, func(row *common.Row) error { + r, err := platform.Engine.Call(engineCtxSuccess, platform.DB, TestChain, "lock_admin", []any{TestUserA, amtDec}, func(row *common.Row) error { return nil }) require.NoError(t, err) @@ -73,7 +70,7 @@ func TestERC20BridgeAdminAuthz(t *testing.T) { } // Step 4: Verify balance was reduced after successful lock_admin - balance, err = testerc20.GetUserBalance(ctx, platform, TestUserA) + balance, err = testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount1, balance, "balance should be reduced after successful lock_admin") diff --git a/tests/extensions/erc20/erc20_bridge_consecutive_test.go b/tests/extensions/erc20/erc20_bridge_consecutive_test.go index 1d65cdc22..91ae18086 100644 --- a/tests/extensions/erc20/erc20_bridge_consecutive_test.go +++ b/tests/extensions/erc20/erc20_bridge_consecutive_test.go @@ -18,17 +18,15 @@ import ( func TestERC20BridgeConsecutiveTests(t *testing.T) { // Test 1: Run first ERC20 test t.Run("first_test", func(t *testing.T) { - seedAndRun(t, "erc20_bridge_first", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - - // Ensure the seeded alias instance exists, active and synced - err := erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, TestEscrowA, TestERC20, 18, 60) + seedAndRun(t, "erc20_bridge_first", func(ctx context.Context, platform *kwilTesting.Platform) error { + // Enable instance with alias for first test + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) require.NoError(t, err) // Simple test: just call info() to verify the extension is working engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 1, false) var syncedResult bool - res, err := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "info", []any{}, func(row *common.Row) error { + res, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "info", []any{}, func(row *common.Row) error { if len(row.Values) >= 7 { syncedResult = row.Values[6].(bool) } @@ -47,15 +45,13 @@ func TestERC20BridgeConsecutiveTests(t *testing.T) { // Test 2: Run second ERC20 test immediately after the first // This test will fail with "already active" if our cleanup doesn't work t.Run("second_test", func(t *testing.T) { - seedAndRun(t, "erc20_bridge_second", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - + seedAndRun(t, "erc20_bridge_second", func(ctx context.Context, platform *kwilTesting.Platform) error { // This should not fail with "already active" error due to proper cleanup - err := erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, TestEscrowA, TestERC20, 18, 60) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) if err != nil { // If this fails, let's check the DB state to debug var isActive bool - checkErr := app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, ` + checkErr := platform.Engine.ExecuteWithoutEngineCtx(ctx, platform.DB, ` {kwil_erc20_meta}SELECT active FROM reward_instances WHERE id = $id `, map[string]any{"id": erc20shim.ForTestingGetInstanceID(TestChain, TestEscrowA)}, func(row *common.Row) error { if len(row.Values) == 1 { @@ -72,7 +68,7 @@ func TestERC20BridgeConsecutiveTests(t *testing.T) { // Simple test: just call info() to verify the extension is working engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 1, false) var syncedResult bool - res, err := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "info", []any{}, func(row *common.Row) error { + res, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "info", []any{}, func(row *common.Row) error { if len(row.Values) >= 7 { syncedResult = row.Values[6].(bool) } diff --git a/tests/extensions/erc20/erc20_bridge_end_to_end_test.go b/tests/extensions/erc20/erc20_bridge_end_to_end_test.go index ae1116d91..6c2ea5e32 100644 --- a/tests/extensions/erc20/erc20_bridge_end_to_end_test.go +++ b/tests/extensions/erc20/erc20_bridge_end_to_end_test.go @@ -28,16 +28,14 @@ import ( // 5) Confirm finalized epochs (ForTestingConfirmAllFinalizedEpochs). // 6) Call alias.list_wallet_rewards(user,false) and assert at least one row returned. func TestERC20BridgeEndToEnd(t *testing.T) { - seedAndRun(t, "erc20_bridge_end_to_end", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - - // Ensure the seeded alias instance exists, active and synced in DB and singleton - require.NoError(t, erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, TestEscrowA, TestERC20, 18, 1)) + seedAndRun(t, "erc20_bridge_end_to_end", func(ctx context.Context, platform *kwilTesting.Platform) error { + // Enable instance with alias for end-to-end test + require.NoError(t, erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 1, TestChain)) // Sanity: ensure the instance reports synced and enabled via info() engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 1, false) var syncedResult, enabledResult bool - resInfo, errInfo := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "info", []any{}, func(row *common.Row) error { + resInfo, errInfo := platform.Engine.Call(engineCtx, platform.DB, TestChain, "info", []any{}, func(row *common.Row) error { if len(row.Values) < 9 { return nil } @@ -53,11 +51,11 @@ func TestERC20BridgeEndToEnd(t *testing.T) { require.True(t, enabledResult, "instance should be enabled before bridge") // Step 1: Inject deposit to give user a balance - err := testerc20.InjectERC20Transfer(ctx, app, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount1, 10, nil) + err := testerc20.InjectERC20Transfer(ctx, platform, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount1, 10, nil) require.NoError(t, err) // Verify user has the balance - balance, err := testerc20.GetUserBalance(ctx, platform, TestUserA) + balance, err := testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount1, balance, "user should have deposit amount") @@ -66,7 +64,7 @@ func TestERC20BridgeEndToEnd(t *testing.T) { amtDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) require.NoError(t, err) - r, err := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "bridge", []any{amtDec}, func(row *common.Row) error { + r, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "bridge", []any{amtDec}, func(row *common.Row) error { return nil }) require.NoError(t, err) @@ -95,12 +93,12 @@ func TestERC20BridgeEndToEnd(t *testing.T) { // Step 3-4: Deterministically finalize current epoch and confirm var bh [32]byte - require.NoError(t, erc20shim.ForTestingFinalizeAndConfirmCurrentEpoch(ctx, app, TestChain, TestEscrowA, 11, bh)) + require.NoError(t, erc20shim.ForTestingFinalizeAndConfirmCurrentEpoch(ctx, platform, TestChain, TestEscrowA, 11, bh)) // Diagnostics: fetch instance id via alias engineCtx = engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 3, false) var instanceID *types.UUID - resID, errID := platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "id", []any{}, func(row *common.Row) error { + resID, errID := platform.Engine.Call(engineCtx, platform.DB, TestChain, "id", []any{}, func(row *common.Row) error { if len(row.Values) != 1 { return nil } @@ -115,7 +113,7 @@ func TestERC20BridgeEndToEnd(t *testing.T) { // Step 5: Query wallet rewards (confirmed only) to verify bridge flow worked deterministically rewardRows := 0 - r, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "list_wallet_rewards", []any{TestUserA, false}, func(row *common.Row) error { + r, err = platform.Engine.Call(engineCtx, platform.DB, TestChain, "list_wallet_rewards", []any{TestUserA, false}, func(row *common.Row) error { rewardRows++ return nil }) diff --git a/tests/extensions/erc20/erc20_bridge_epoch_test.go b/tests/extensions/erc20/erc20_bridge_epoch_test.go index f18222740..fa5e3d1be 100644 --- a/tests/extensions/erc20/erc20_bridge_epoch_test.go +++ b/tests/extensions/erc20/erc20_bridge_epoch_test.go @@ -4,7 +4,6 @@ package tests import ( "context" - "fmt" "testing" "github.com/stretchr/testify/require" @@ -18,9 +17,7 @@ import ( // TestERC20BridgeEpochFlow validates lock-and-issue, finalize and confirm using shims. func TestERC20BridgeEpochFlow(t *testing.T) { - seedAndRun(t, "erc20_bridge_epoch_flow", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - + seedAndRun(t, "erc20_bridge_epoch_flow", func(ctx context.Context, platform *kwilTesting.Platform) error { // Singleton reset and initialize is handled by seedAndRun // Use the sepolia_bridge alias created by simple_mock.sql, but create a separate instance for this test @@ -31,29 +28,20 @@ func TestERC20BridgeEpochFlow(t *testing.T) { user := "0xabc0000000000000000000000000000000000001" value := "500000000000000000" // 0.5 - // Ensure active+synced in-memory using the new helper - require.NoError(t, erc20shim.ForTestingActivateAndInitialize(ctx, app, chain, escrow, erc20, 18, 1)) - - // Create an alias for this instance - err := app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` - USE erc20 { - chain: '%s', - escrow: '%s' - } AS epoch_test_bridge - `, chain, escrow), nil, nil) - require.NoError(t, err) + // Enable instance with alias (includes alias creation, activation via period + rehydrate), idempotently and collision-free + require.NoError(t, erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, chain, escrow, erc20, 18, 1, TestChain)) // Credit balance via injected transfer (simulates inbound deposit) - require.NoError(t, testerc20.InjectERC20Transfer(ctx, app, chain, escrow, erc20, user, escrow, value, 10, nil)) + require.NoError(t, testerc20.InjectERC20Transfer(ctx, platform, chain, escrow, erc20, user, escrow, value, 10, nil)) // Lock and issue directly into epoch (simulate bridge request) - require.NoError(t, erc20shim.ForTestingLockAndIssueDirect(ctx, app, chain, escrow, user, value)) + require.NoError(t, erc20shim.ForTestingLockAndIssueDirect(ctx, platform, chain, escrow, user, value)) // Assert pre-finalize: there should be a pending reward in epoch_rewards preQ := ` {kwil_erc20_meta}SELECT count(*) FROM epoch_rewards` var preRows int - err = platform.Engine.ExecuteWithoutEngineCtx(ctx, platform.DB, preQ, nil, func(row *common.Row) error { + err := platform.Engine.ExecuteWithoutEngineCtx(ctx, platform.DB, preQ, nil, func(row *common.Row) error { if len(row.Values) != 1 { return nil } @@ -65,7 +53,7 @@ func TestERC20BridgeEpochFlow(t *testing.T) { // Finalize and confirm via helper var bh [32]byte - require.NoError(t, erc20shim.ForTestingFinalizeAndConfirmCurrentEpoch(ctx, app, chain, escrow, 11, bh)) + require.NoError(t, erc20shim.ForTestingFinalizeAndConfirmCurrentEpoch(ctx, platform, chain, escrow, 11, bh)) // Query confirmed rewards directly q := ` @@ -82,8 +70,8 @@ func TestERC20BridgeEpochFlow(t *testing.T) { require.NoError(t, err) require.Greater(t, rows, 0, "expected confirmed wallet reward rows") - // Cleanup: Deactivate the test instance - testerc20.DeactivateCurrentInstanceTx(t, platform, escrow) + // Cleanup: Disable the test instance (tears down runtimes + resets singleton) + require.NoError(t, erc20shim.ForTestingDisableInstance(ctx, platform, chain, escrow, TestChain)) return nil }) diff --git a/tests/extensions/erc20/erc20_bridge_injection_test.go b/tests/extensions/erc20/erc20_bridge_injection_test.go index 1d4772fca..387750dd3 100644 --- a/tests/extensions/erc20/erc20_bridge_injection_test.go +++ b/tests/extensions/erc20/erc20_bridge_injection_test.go @@ -17,9 +17,7 @@ import ( // TestERC20BridgeInjectedTransferAffectsBalance uses the production code path via ordered-sync/evm-sync shims. func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { - seedAndRun(t, "erc20_bridge_injected_transfer_affects_balance", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - + seedAndRun(t, "erc20_bridge_injected_transfer_affects_balance", func(ctx context.Context, platform *kwilTesting.Platform) error { // Use a different escrow for this test to avoid conflicts with seeded instance chain := "sepolia" escrow := "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" @@ -27,24 +25,12 @@ func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { user := "0xabc0000000000000000000000000000000000001" value := "1000000000000000000" - // Ensure instance synced and create alias - _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20, 18) - require.NoError(t, err) - - // Load instances into singleton to avoid duplicate prepare on USE - require.NoError(t, erc20shim.ForTestingInitializeExtension(ctx, app)) - - // Create an alias for this instance - err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` - USE erc20 { - chain: '%s', - escrow: '%s' - } AS injection_test_bridge - `, chain, escrow), nil, nil) + // Enable instance with alias for injection test + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, chain, escrow, erc20, 18, 60, TestChain) require.NoError(t, err) // Inject a transfer: from user to escrow (lock/credit path) - err = testerc20.InjectERC20Transfer(ctx, app, chain, escrow, erc20, user, escrow, value, 1, nil) + err = testerc20.InjectERC20Transfer(ctx, platform, chain, escrow, erc20, user, escrow, value, 1, nil) require.NoError(t, err) // Query balance via the test alias @@ -58,7 +44,7 @@ func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { engCtx := &common.EngineContext{TxContext: txCtx} var got string - r, err := platform.Engine.Call(engCtx, platform.DB, "injection_test_bridge", "balance", []any{user}, func(row *common.Row) error { + r, err := platform.Engine.Call(engCtx, platform.DB, TestChain, "balance", []any{user}, func(row *common.Row) error { if len(row.Values) != 1 { return fmt.Errorf("expected 1 column, got %d", len(row.Values)) } @@ -73,7 +59,7 @@ func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { require.Equal(t, value, got, "expected balance to reflect injected transfer amount") // Cleanup: Deactivate the test instance - testerc20.DeactivateCurrentInstanceTx(t, platform, escrow) + erc20shim.ForTestingDisableInstance(ctx, platform, chain, escrow, TestChain) return nil }) diff --git a/tests/extensions/erc20/erc20_bridge_multi_instance_test.go b/tests/extensions/erc20/erc20_bridge_multi_instance_test.go index 23c647541..7a2c02704 100644 --- a/tests/extensions/erc20/erc20_bridge_multi_instance_test.go +++ b/tests/extensions/erc20/erc20_bridge_multi_instance_test.go @@ -4,7 +4,6 @@ package tests import ( "context" - "fmt" "testing" "github.com/stretchr/testify/require" @@ -26,40 +25,22 @@ import ( // 3) Inject deposit only for escrowA, userX. // 4) Assert aliasA.balance(userX) > 0 and aliasB.balance(userX) == 0. func TestERC20BridgeMultiInstanceIsolation(t *testing.T) { - seedAndRun(t, "erc20_bridge_multi_instance_isolation", "", func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - + seedAndRun(t, "erc20_bridge_multi_instance_isolation", func(ctx context.Context, platform *kwilTesting.Platform) error { // Use distinct escrow addresses to avoid conflicts with seed files and other tests + aliasA := "aliasA" + aliasB := "aliasB" escrowA := "0xffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" escrowB := "0xffbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" // Initialize both instances active+synced - err := erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, escrowA, TestERC20, 18, 60) - require.NoError(t, err) - err = erc20shim.ForTestingActivateAndInitialize(ctx, app, TestChain, escrowB, TestERC20, 18, 60) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, escrowA, TestERC20, 18, 60, aliasA) require.NoError(t, err) - - // Create aliasA for escrowA - err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` - USE erc20 { - chain: '%s', - escrow: '%s' - } AS aliasA - `, TestChain, escrowA), nil, nil) - require.NoError(t, err) - - // Create aliasB for escrowB - err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, fmt.Sprintf(` - USE erc20 { - chain: '%s', - escrow: '%s' - } AS aliasB - `, TestChain, escrowB), nil, nil) + err = erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, escrowB, TestERC20, 18, 60, aliasB) require.NoError(t, err) // Step 1: Inject deposit only for escrowA, userX // This simulates a deposit to escrowA only - err = testerc20.InjectERC20Transfer(ctx, app, TestChain, escrowA, TestERC20, TestUserA, escrowA, TestAmount1, 10, nil) + err = testerc20.InjectERC20Transfer(ctx, platform, TestChain, escrowA, TestERC20, TestUserA, escrowA, TestAmount1, 10, nil) require.NoError(t, err) // Step 2: Verify isolation - aliasA should have balance, aliasB should not @@ -67,7 +48,7 @@ func TestERC20BridgeMultiInstanceIsolation(t *testing.T) { // Check balance in aliasA (should have deposit) var balanceA *types.Decimal - r, err := platform.Engine.Call(engineCtx, platform.DB, "aliasA", "balance", []any{TestUserA}, func(row *common.Row) error { + r, err := platform.Engine.Call(engineCtx, platform.DB, aliasA, "balance", []any{TestUserA}, func(row *common.Row) error { if len(row.Values) != 1 { return nil } @@ -83,7 +64,7 @@ func TestERC20BridgeMultiInstanceIsolation(t *testing.T) { // Check balance in aliasB (should be zero - no deposit made) var balanceB *types.Decimal - r, err = platform.Engine.Call(engineCtx, platform.DB, "aliasB", "balance", []any{TestUserA}, func(row *common.Row) error { + r, err = platform.Engine.Call(engineCtx, platform.DB, aliasB, "balance", []any{TestUserA}, func(row *common.Row) error { if len(row.Values) != 1 { return nil } @@ -98,8 +79,8 @@ func TestERC20BridgeMultiInstanceIsolation(t *testing.T) { require.Equal(t, "0", balanceB.String(), "aliasB should have zero balance (no deposit made)") // Cleanup: Deactivate both instances for test isolation - testerc20.DeactivateCurrentInstanceTx(t, platform, escrowA) - testerc20.DeactivateCurrentInstanceTx(t, platform, escrowB) + erc20shim.ForTestingDisableInstance(ctx, platform, TestChain, escrowA, aliasA) + erc20shim.ForTestingDisableInstance(ctx, platform, TestChain, escrowB, aliasB) return nil }) diff --git a/tests/extensions/erc20/erc20_bridge_simple_test.go b/tests/extensions/erc20/erc20_bridge_simple_test.go index 0b641de60..cda2cca09 100644 --- a/tests/extensions/erc20/erc20_bridge_simple_test.go +++ b/tests/extensions/erc20/erc20_bridge_simple_test.go @@ -4,65 +4,27 @@ package tests import ( "context" - "fmt" - "path/filepath" - "runtime" "testing" "github.com/stretchr/testify/require" + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" kwilTesting "github.com/trufnetwork/kwil-db/testing" - "github.com/trufnetwork/node/internal/migrations" - testutils "github.com/trufnetwork/node/tests/streams/utils" testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" ) -// WithERC20TestSetup is a helper function that sets up the test environment with ERC-20 bridge -// Follows the same pattern as WithQueryTestSetup in query_test.go for consistency -func WithERC20TestSetup(escrowAddr string) func(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error { - setupFunc := testerc20.WithERC20TestSetup(escrowAddr) - return func(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error { - return func(ctx context.Context, platform *kwilTesting.Platform) error { - // First run the ERC20 setup - err := setupFunc(ctx, platform) - if err != nil { - return err - } - // Then run the test function - return testFn(ctx, platform) - } - } -} - // TestERC20BridgeSimpleBalanceTx uses transaction-based isolation for perfect test isolation // This allows reusing the same user accounts and escrow addresses across tests. +// Creates the extension alias during test execution instead of using seed scripts. func TestERC20BridgeSimpleBalanceTx(t *testing.T) { - // Compute absolute path to the seed script relative to this test file - _, thisFile, _, _ := runtime.Caller(0) - seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") - - seedScripts := migrations.GetSeedScriptPaths() - seedScripts = append(seedScripts, seedPath) - - testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: "erc20_bridge_simple_balance_tx", - SeedScripts: seedScripts, - FunctionTests: []kwilTesting.TestFunc{ - func(ctx context.Context, platform *kwilTesting.Platform) error { - // Use transaction-based isolation with WithTx - testFunc := testutils.WithTx(platform, func(t *testing.T, txPlatform *kwilTesting.Platform) { - // Setup ERC20 with the default escrow from simple_mock.sql - testerc20.WithERC20TestSetupTx("0x1111111111111111111111111111111111111111")(t, txPlatform) + seedAndRun(t, "erc20_bridge_simple_balance_tx", func(ctx context.Context, platform *kwilTesting.Platform) error { + // Enable instance with alias in one step + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) + require.NoError(t, err) - // Now run the actual test logic with the transaction-scoped platform - testSimpleBalanceTx(t, txPlatform) - }) - - // Execute the transaction-wrapped test - testFunc(t) - return nil - }, - }, - }, &testutils.Options{Options: testutils.GetTestOptions()}) + // Run the actual test logic + testSimpleBalanceTx(t, platform) + return nil + }) } // testSimpleBalanceTx performs the actual balance test logic with transaction-scoped platform @@ -71,31 +33,13 @@ func testSimpleBalanceTx(t *testing.T, txPlatform *kwilTesting.Platform) { const testWallet = "0x1111111111111111111111111111111111110001" // Query balance for a wallet with no prior deposits - balance, err := testerc20.GetUserBalance(context.Background(), txPlatform, testWallet) + balance, err := testerc20.GetUserBalance(context.Background(), txPlatform, TestChain, testWallet) require.NoError(t, err) // Should return "0" for wallet with no prior deposits require.Equal(t, "0", balance, "expected balance of 0 for wallet with no prior deposits") } -// testSimpleBalance performs the actual balance test logic (legacy version) -func testSimpleBalance(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { - return func(ctx context.Context, platform *kwilTesting.Platform) error { - // Test-specific constants - const testWallet = "0x1111111111111111111111111111111111110001" - - // Query balance for a wallet with no prior deposits - balance, err := testerc20.GetUserBalance(ctx, platform, testWallet) - if err != nil { - return fmt.Errorf("failed to get user balance: %w", err) - } - - // Should return "0" for wallet with no prior deposits - require.Equal(t, "0", balance, "expected balance of 0 for wallet with no prior deposits") - return nil - } -} - // Removed complex listener tests - keeping only simple extension functionality tests // that follow the same pattern as query_test.go for consistency @@ -105,40 +49,23 @@ func testSimpleBalance(t *testing.T) func(ctx context.Context, platform *kwilTes // // Test Scenario: // 1. Set up ERC-20 bridge extension with a synced instance -// 2. Credit user's balance with a realistic ERC-20 transfer (simulating deposit) -// 3. Call lock_admin to lock the user's tokens -// 4. Verify user's balance is reduced to 0 +// 2. Create extension alias during test execution +// 3. Credit user's balance with a realistic ERC-20 transfer (simulating deposit) +// 4. Call lock_admin to lock the user's tokens +// 5. Verify user's balance is reduced to 0 // // This test ensures admin lock functionality works correctly and integrates // with the extension's balance tracking. func TestERC20BridgeAdminLockAffectsBalanceTx(t *testing.T) { - // Compute absolute path to the seed script relative to this test file - _, thisFile, _, _ := runtime.Caller(0) - seedPath := filepath.Join(filepath.Dir(thisFile), "simple_mock.sql") - - seedScripts := migrations.GetSeedScriptPaths() - seedScripts = append(seedScripts, seedPath) + seedAndRun(t, "erc20_bridge_admin_lock_affects_balance_tx", func(ctx context.Context, platform *kwilTesting.Platform) error { + // Enable instance with alias in one step + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) + require.NoError(t, err) - testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: "erc20_bridge_admin_lock_affects_balance_tx", - SeedScripts: seedScripts, - FunctionTests: []kwilTesting.TestFunc{ - func(ctx context.Context, platform *kwilTesting.Platform) error { - // Use transaction-based isolation with WithTx - testFunc := testutils.WithTx(platform, func(t *testing.T, txPlatform *kwilTesting.Platform) { - // Setup ERC20 with the default escrow from simple_mock.sql - testerc20.WithERC20TestSetupTx("0x1111111111111111111111111111111111111111")(t, txPlatform) - - // Now run the actual test logic with the transaction-scoped platform - testAdminLockTx(t, txPlatform) - }) - - // Execute the transaction-wrapped test - testFunc(t) - return nil - }, - }, - }, &testutils.Options{Options: testutils.GetTestOptions()}) + // Run the actual test logic + testAdminLockTx(t, platform) + return nil + }) } // testAdminLockTx performs the actual admin lock test logic with transaction-scoped platform @@ -152,7 +79,7 @@ func testAdminLockTx(t *testing.T, txPlatform *kwilTesting.Platform) { // Step 1: Pre-credit user's balance with realistic ERC-20 transfer // This simulates a user depositing tokens into the bridge escrow // Use the same escrow address as the test setup - err := testerc20.CreditUserBalance(context.Background(), txPlatform, "0x1111111111111111111111111111111111111111", testUser, testAmount) + err := testerc20.CreditUserBalance(context.Background(), txPlatform, TestChain, TestEscrowA, testUser, testAmount) require.NoError(t, err) // Step 2: Execute admin lock operation @@ -162,7 +89,7 @@ func testAdminLockTx(t *testing.T, txPlatform *kwilTesting.Platform) { // Step 3: Verify balance was correctly reduced // After locking all tokens, balance should be 0 - finalBalance, err := testerc20.GetUserBalance(context.Background(), txPlatform, testUser) + finalBalance, err := testerc20.GetUserBalance(context.Background(), txPlatform, TestChain, testUser) require.NoError(t, err) require.Equal(t, "0", finalBalance, "expected user balance to be zero after admin lock of entire balance") diff --git a/tests/extensions/erc20/erc20_bridge_transfer_test.go b/tests/extensions/erc20/erc20_bridge_transfer_test.go index 60ca2caf2..1aecac85d 100644 --- a/tests/extensions/erc20/erc20_bridge_transfer_test.go +++ b/tests/extensions/erc20/erc20_bridge_transfer_test.go @@ -25,28 +25,22 @@ import ( // 3) Inject deposit: from userA -> escrow; assert alias.balance(userA) == injected. // 4) Call alias.transfer(to=userB, amount=injected/2); assert balances for userA, userB updated accordingly. func TestERC20BridgeTransferBalances(t *testing.T) { - seedAndRun(t, "erc20_bridge_transfer_balances", "simple_mock.sql", func(ctx context.Context, platform *kwilTesting.Platform) error { - app := &common.App{DB: platform.DB, Engine: platform.Engine} - - // Singleton reset and initialize is handled by seedAndRun - // The seeded instance may have been deactivated, so we need to reactivate it - _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, TestChain, TestEscrowA, TestERC20, 18) - require.NoError(t, err) - - err = erc20shim.ForTestingInitializeExtension(ctx, app) + seedAndRun(t, "erc20_bridge_transfer_balances", func(ctx context.Context, platform *kwilTesting.Platform) error { + // Enable instance with alias for transfer test + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) require.NoError(t, err) // Step 1: Inject deposit for userA - err = testerc20.InjectERC20Transfer(ctx, app, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount2, 10, nil) + err = testerc20.InjectERC20Transfer(ctx, platform, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount2, 10, nil) require.NoError(t, err) // Verify userA received the full deposit - balanceA, err := testerc20.GetUserBalance(ctx, platform, TestUserA) + balanceA, err := testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount2, balanceA, "userA should have full deposit amount") // Verify userB has zero balance initially - balanceB, err := testerc20.GetUserBalance(ctx, platform, TestUserB) + balanceB, err := testerc20.GetUserBalance(ctx, platform, TestChain, TestUserB) require.NoError(t, err) require.Equal(t, "0", balanceB, "userB should have zero balance initially") @@ -57,19 +51,19 @@ func TestERC20BridgeTransferBalances(t *testing.T) { halfDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) require.NoError(t, err) - _, err = platform.Engine.Call(engineCtx, platform.DB, "sepolia_bridge", "transfer", []any{TestUserB, halfDec}, func(row *common.Row) error { + _, err = platform.Engine.Call(engineCtx, platform.DB, TestChain, "transfer", []any{TestUserB, halfDec}, func(row *common.Row) error { return nil }) require.NoError(t, err) // Step 3: Verify balances after transfer // userA should have remaining amount - balanceA, err = testerc20.GetUserBalance(ctx, platform, TestUserA) + balanceA, err = testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount1, balanceA, "userA should have remaining amount after transfer") // userB should have received the transferred amount - balanceB, err = testerc20.GetUserBalance(ctx, platform, TestUserB) + balanceB, err = testerc20.GetUserBalance(ctx, platform, TestChain, TestUserB) require.NoError(t, err) require.Equal(t, TestAmount1, balanceB, "userB should have received transferred amount") diff --git a/tests/extensions/erc20/simple_mock.sql b/tests/extensions/erc20/simple_mock.sql deleted file mode 100644 index 773359e8f..000000000 --- a/tests/extensions/erc20/simple_mock.sql +++ /dev/null @@ -1,11 +0,0 @@ --- we'll use this to test configuration of the erc20 bridge in tests. --- if we successfully emit events and this listens, then we know the bridge is configured correctly. -USE erc20 { - chain: 'sepolia', - escrow: '0x1111111111111111111111111111111111111111' -} AS sepolia_bridge; - -CREATE ACTION get_balance($wallet_address TEXT) public RETURNS (balance DECIMAL(78, 0)) { - $balance := sepolia_bridge.balance($wallet_address); - return $balance; -} \ No newline at end of file diff --git a/tests/streams/utils/erc20/helper.go b/tests/streams/utils/erc20/helper.go index c5e309bf9..0c3fe6cda 100644 --- a/tests/streams/utils/erc20/helper.go +++ b/tests/streams/utils/erc20/helper.go @@ -15,71 +15,32 @@ import ( kwilTesting "github.com/trufnetwork/kwil-db/testing" ) -// WithERC20TestSetup is a helper function that sets up the test environment with ERC-20 bridge -// This version works with transaction-based isolation for perfect test isolation -func WithERC20TestSetup(escrowAddr string) func(ctx context.Context, platform *kwilTesting.Platform) error { - return func(ctx context.Context, platform *kwilTesting.Platform) error { - // Use the shared platform's DB and Engine (could be transaction-scoped) - app := &common.App{DB: platform.DB, Engine: platform.Engine} - - // Reset the entire singleton to ensure test isolation - erc20shim.ForTestingResetSingleton() - - // Initialize extension with synced instance - _, err := erc20shim.ForTestingForceSyncInstance(ctx, app, "sepolia", escrowAddr, "0x2222222222222222222222222222222222222222", 18) - if err != nil { - return fmt.Errorf("failed to force sync ERC20 instance for escrow %s: %w", escrowAddr, err) - } - - err = erc20shim.ForTestingInitializeExtension(ctx, app) - if err != nil { - return fmt.Errorf("failed to initialize ERC20 extension: %w", err) - } - - return nil - } -} - // WithERC20TestSetupTx is a transaction-aware version that works with WithTx for perfect isolation -func WithERC20TestSetupTx(escrowAddr string) func(t *testing.T, txPlatform *kwilTesting.Platform) { +func WithERC20TestSetup(alias string, escrowAddr string) func(t *testing.T, txPlatform *kwilTesting.Platform) { return func(t *testing.T, txPlatform *kwilTesting.Platform) { // Use the transaction-scoped platform - app := &common.App{DB: txPlatform.DB, Engine: txPlatform.Engine} - - // Step 1: Reset the entire singleton for this test - erc20shim.ForTestingResetSingleton() - // Step 2: Initialize extension with synced instance using transaction-scoped DB - _, err := erc20shim.ForTestingForceSyncInstance(context.Background(), app, "sepolia", escrowAddr, "0x2222222222222222222222222222222222222222", 18) - if err != nil { - t.Fatalf("failed to force sync ERC20 instance for escrow %s: %v", escrowAddr, err) - } - - // Step 3: Initialize extension with synced instance using transaction-scoped DB - err = erc20shim.ForTestingInitializeExtension(context.Background(), app) - if err != nil { - t.Fatalf("failed to initialize ERC20 extension: %v", err) - } - - // Step 4: Set up cleanup to deactivate the instance after the test + // Step 1: Set up cleanup to deactivate the instance after the test // This ensures the next test can reuse the same escrow address t.Cleanup(func() { - DeactivateCurrentInstanceTx(t, txPlatform, escrowAddr) + erc20shim.ForTestingDisableInstance(context.Background(), txPlatform, alias, escrowAddr, alias) + erc20shim.ForTestingResetSingleton() }) + + erc20shim.ForTestingSeedAndActivateInstance(context.Background(), txPlatform, alias, escrowAddr, "0x2222222222222222222222222222222222222222", 18, 60, alias) } } // CreditUserBalance injects a realistic ERC-20 transfer to credit the user's balance. // This simulates a user depositing tokens into the bridge. -func CreditUserBalance(ctx context.Context, platform *kwilTesting.Platform, escrowAddr, userAddr, amount string) error { +func CreditUserBalance(ctx context.Context, platform *kwilTesting.Platform, extensionAlias, escrowAddr, userAddr, amount string) error { // Use the platform's DB and Engine (could be transaction-scoped) - app := &common.App{DB: platform.DB, Engine: platform.Engine} return InjectERC20Transfer( - ctx, app, "sepolia", escrowAddr, "0x2222222222222222222222222222222222222222", userAddr, escrowAddr, amount, 10, nil) + ctx, platform, extensionAlias, escrowAddr, "0x2222222222222222222222222222222222222222", userAddr, escrowAddr, amount, 10, nil) } // GetUserBalance queries the user's current balance via the extension. -func GetUserBalance(ctx context.Context, platform *kwilTesting.Platform, userAddr string) (string, error) { +func GetUserBalance(ctx context.Context, platform *kwilTesting.Platform, extensionAlias, userAddr string) (string, error) { txCtx := &common.TxContext{ Ctx: ctx, BlockContext: &common.BlockContext{Height: 1}, @@ -90,7 +51,7 @@ func GetUserBalance(ctx context.Context, platform *kwilTesting.Platform, userAdd engCtx := &common.EngineContext{TxContext: txCtx, OverrideAuthz: true} var balance string - r, err := platform.Engine.Call(engCtx, platform.DB, "", "get_balance", []any{userAddr}, func(row *common.Row) error { + r, err := platform.Engine.Call(engCtx, platform.DB, extensionAlias, "balance", []any{userAddr}, func(row *common.Row) error { if len(row.Values) != 1 { return fmt.Errorf("expected 1 column, got %d", len(row.Values)) } @@ -137,44 +98,3 @@ func CallLockAdmin(ctx context.Context, platform *kwilTesting.Platform, userAddr } return nil } - -// DeactivateCurrentInstanceTx deactivates the ERC20 instance for the given escrow. -// This should be called in test cleanup to ensure the next test can use the same escrow. -// It sets active=false in the DB and refreshes the singleton state. -func DeactivateCurrentInstanceTx(t *testing.T, txPlatform *kwilTesting.Platform, escrowAddr string) { - app := &common.App{DB: txPlatform.DB, Engine: txPlatform.Engine} - - // Get the instance ID by calling ForTestingForceSyncInstance (it's idempotent) - id, err := erc20shim.ForTestingForceSyncInstance(context.Background(), app, "sepolia", escrowAddr, "0x2222222222222222222222222222222222222222", 18) - if err != nil { - t.Logf("Warning: failed to get instance ID for deactivation: %v", err) - return - } - - // Call the meta extension's disable method to set active=false - txCtx := &common.TxContext{ - Ctx: context.Background(), - BlockContext: &common.BlockContext{Height: 1}, - Signer: txPlatform.Deployer, - Caller: "0x0000000000000000000000000000000000000000", - TxID: txPlatform.Txid(), - } - engCtx := &common.EngineContext{TxContext: txCtx, OverrideAuthz: true} - - // Call the meta extension's disable method - _, err = txPlatform.Engine.Call(engCtx, txPlatform.DB, "kwil_erc20_meta", "disable", []any{id}, func(row *common.Row) error { - return nil - }) - if err != nil { - t.Logf("Warning: failed to deactivate instance %s: %v", id, err) - // Don't fail the test for cleanup issues, just log - } - - // Reset and re-initialize the singleton to reflect the deactivated state - erc20shim.ForTestingResetSingleton() - err = erc20shim.ForTestingInitializeExtension(context.Background(), app) - if err != nil { - t.Logf("Warning: failed to re-initialize singleton after deactivation: %v", err) - // Don't fail the test for cleanup issues, just log - } -} diff --git a/tests/streams/utils/erc20/inject.go b/tests/streams/utils/erc20/inject.go index e513bce08..905ec90d1 100644 --- a/tests/streams/utils/erc20/inject.go +++ b/tests/streams/utils/erc20/inject.go @@ -15,12 +15,13 @@ import ( erc20bridge "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" evmsync "github.com/trufnetwork/kwil-db/node/exts/evm-sync" orderedsync "github.com/trufnetwork/kwil-db/node/exts/ordered-sync" + kwilTesting "github.com/trufnetwork/kwil-db/testing" ) // InjectERC20Transfer forces an instance synced and injects a synthetic Transfer log that credits balance. -func InjectERC20Transfer(ctx context.Context, app *common.App, chain, escrow, erc20Addr, fromHex, toHex string, valueStr string, point int64, prev *int64) error { +func InjectERC20Transfer(ctx context.Context, platform *kwilTesting.Platform, chain, escrow, erc20Addr, fromHex, toHex string, valueStr string, point int64, prev *int64) error { // 1) Ensure instance exists and is synced - id, err := erc20bridge.ForTestingForceSyncInstance(ctx, app, chain, escrow, erc20Addr, 18) + id, err := erc20bridge.ForTestingForceSyncInstance(ctx, platform, chain, escrow, erc20Addr, 18) if err != nil { return fmt.Errorf("force sync instance: %w", err) } @@ -63,12 +64,12 @@ func InjectERC20Transfer(ctx context.Context, app *common.App, chain, escrow, er return fmt.Errorf("serialize logs: %w", err) } - if err := orderedsync.ForTestingStoreLogs(ctx, app, topic, logsData, point, prev); err != nil { + if err := orderedsync.ForTestingStoreLogs(ctx, platform, topic, logsData, point, prev); err != nil { return fmt.Errorf("store logs: %w", err) } // 5) Resolve via end-block path - if err := orderedsync.ForTestingResolve(ctx, app, &common.BlockContext{Height: point, Timestamp: point}); err != nil { + if err := orderedsync.ForTestingResolve(ctx, platform, &common.BlockContext{Height: point, Timestamp: point}); err != nil { return fmt.Errorf("resolve: %w", err) } From a65927b95c3d9b5d71e7033cb6ccbfa62f6965bc Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Thu, 11 Sep 2025 17:04:14 -0300 Subject: [PATCH 09/18] fix(ci): update Go test command to include 'kwiltest' build tag for improved test targeting This commit modifies the CI workflow configuration to include the 'kwiltest' build tag in the Go test command. This change aims to enhance the testing process by allowing for more specific test execution, ensuring that tests relevant to the 'kwiltest' context are run effectively. The adjustment is part of ongoing efforts to improve the reliability and maintainability of the testing framework. --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 46c445cc9..7fcaa08a0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -89,7 +89,7 @@ jobs: sleep 5 fi - if go test -short -p 1 -timeout=15m -count=1 ./...; then + if go test -short -p 1 -timeout=15m -count=1 -tags=kwiltest ./...; then echo "✅ Tests passed on attempt $attempt" break else From a7e4bb4b73f4500dd7c0ef6c82487e1bbe0c1f3c Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Thu, 11 Sep 2025 17:22:40 -0300 Subject: [PATCH 10/18] refactor(tests): update CallLockAdmin function signature and improve test clarity This commit modifies the `CallLockAdmin` function in the ERC-20 helper to include an `extensionAlias` parameter, enhancing its flexibility for different bridge instances. Additionally, the `erc20_bridge_simple_test.go` and `erc20_bridge_epoch_test.go` files are updated to reflect this change, ensuring that the correct alias is used during admin lock operations. These adjustments aim to improve the clarity and maintainability of the test suite. --- tests/extensions/erc20/erc20_bridge_epoch_test.go | 1 - tests/extensions/erc20/erc20_bridge_simple_test.go | 2 +- tests/streams/utils/erc20/helper.go | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/extensions/erc20/erc20_bridge_epoch_test.go b/tests/extensions/erc20/erc20_bridge_epoch_test.go index fa5e3d1be..c6e13548b 100644 --- a/tests/extensions/erc20/erc20_bridge_epoch_test.go +++ b/tests/extensions/erc20/erc20_bridge_epoch_test.go @@ -19,7 +19,6 @@ import ( func TestERC20BridgeEpochFlow(t *testing.T) { seedAndRun(t, "erc20_bridge_epoch_flow", func(ctx context.Context, platform *kwilTesting.Platform) error { // Singleton reset and initialize is handled by seedAndRun - // Use the sepolia_bridge alias created by simple_mock.sql, but create a separate instance for this test // Use a different escrow for this test to avoid conflicts with seeded instance chain := "sepolia" diff --git a/tests/extensions/erc20/erc20_bridge_simple_test.go b/tests/extensions/erc20/erc20_bridge_simple_test.go index cda2cca09..f5cf17e0b 100644 --- a/tests/extensions/erc20/erc20_bridge_simple_test.go +++ b/tests/extensions/erc20/erc20_bridge_simple_test.go @@ -84,7 +84,7 @@ func testAdminLockTx(t *testing.T, txPlatform *kwilTesting.Platform) { // Step 2: Execute admin lock operation // This should reduce user's balance by the locked amount - err = testerc20.CallLockAdmin(context.Background(), txPlatform, testUser, testAmount) + err = testerc20.CallLockAdmin(context.Background(), txPlatform, TestChain, testUser, testAmount) require.NoError(t, err) // Step 3: Verify balance was correctly reduced diff --git a/tests/streams/utils/erc20/helper.go b/tests/streams/utils/erc20/helper.go index 0c3fe6cda..4c0f80593 100644 --- a/tests/streams/utils/erc20/helper.go +++ b/tests/streams/utils/erc20/helper.go @@ -69,7 +69,7 @@ func GetUserBalance(ctx context.Context, platform *kwilTesting.Platform, extensi // CallLockAdmin executes the lock_admin system method with OverrideAuthz. // This simulates an admin locking user tokens. -func CallLockAdmin(ctx context.Context, platform *kwilTesting.Platform, userAddr, amount string) error { +func CallLockAdmin(ctx context.Context, platform *kwilTesting.Platform, extensionAlias, userAddr, amount string) error { txCtx := &common.TxContext{ Ctx: ctx, BlockContext: &common.BlockContext{Height: 1}, @@ -90,7 +90,7 @@ func CallLockAdmin(ctx context.Context, platform *kwilTesting.Platform, userAddr } amtDec.SetPrecisionAndScale(78, 0) - _, err = platform.Engine.Call(engCtx, platform.DB, "sepolia_bridge", "lock_admin", []any{userAddr, amtDec}, func(row *common.Row) error { + _, err = platform.Engine.Call(engCtx, platform.DB, extensionAlias, "lock_admin", []any{userAddr, amtDec}, func(row *common.Row) error { return nil }) if err != nil { From c07094c112e1629da3b6c3033a033c4fbcf92593 Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Thu, 11 Sep 2025 22:47:33 -0300 Subject: [PATCH 11/18] chore(deps): update kwil-db and go-ethereum dependencies in go.mod and go.sum This commit updates the versions of `kwil-db` and `kwil-db/core` in the `go.mod` and `go.sum` files to the latest revisions. Additionally, it ensures that the `go-ethereum` dependency is correctly listed without the indirect comment. These changes aim to keep the project dependencies up to date and maintain compatibility with the latest features and fixes. --- go.mod | 6 +-- go.sum | 8 +-- .../erc20/erc20_bridge_actions_smoke_test.go | 1 - .../erc20/erc20_bridge_injection_test.go | 8 +-- .../erc20/erc20_bridge_multi_instance_test.go | 16 ++++-- .../erc20/erc20_bridge_transfer_test.go | 5 +- tests/streams/complex_composed_test.go | 8 +-- tests/streams/utils/cache/helper.go | 3 ++ tests/streams/utils/erc20/config.go | 12 ++--- tests/streams/utils/erc20/helper.go | 14 ++--- tests/streams/utils/erc20/inject.go | 2 + tests/streams/utils/eventstore/mock.go | 54 +++++++++++++------ tests/streams/utils/runner.go | 14 ++--- 13 files changed, 93 insertions(+), 58 deletions(-) diff --git a/go.mod b/go.mod index 19350221b..804e54a33 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,8 @@ require ( github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 github.com/testcontainers/testcontainers-go v0.37.0 - github.com/trufnetwork/kwil-db v0.10.3-0.20250905175054-602e824e33c2 - github.com/trufnetwork/kwil-db/core v0.4.3-0.20250905175054-602e824e33c2 + github.com/trufnetwork/kwil-db v0.10.3-0.20250911225741-d6cb2b2747ff + github.com/trufnetwork/kwil-db/core v0.4.3-0.20250911225741-d6cb2b2747ff github.com/trufnetwork/sdk-go v0.3.2-0.20250630062504-841b40cdb709 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa @@ -82,7 +82,7 @@ require ( github.com/ebitengine/purego v0.8.2 // indirect github.com/elastic/gosigar v0.14.3 // indirect github.com/ethereum/c-kzg-4844 v1.0.2 // indirect - github.com/ethereum/go-ethereum v1.14.13 // indirect + github.com/ethereum/go-ethereum v1.14.13 github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/go.sum b/go.sum index 666ae848f..659d6787b 100644 --- a/go.sum +++ b/go.sum @@ -1212,10 +1212,10 @@ github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZ github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= -github.com/trufnetwork/kwil-db v0.10.3-0.20250905175054-602e824e33c2 h1:sWkNssIA46NoEYD8db4zTHbXJecPvlm8OWbYbyW21Es= -github.com/trufnetwork/kwil-db v0.10.3-0.20250905175054-602e824e33c2/go.mod h1:LiBAC48uZl2B0IiLtD2hpOce7RNfpuDdghVAOc3u1Qo= -github.com/trufnetwork/kwil-db/core v0.4.3-0.20250905175054-602e824e33c2 h1:ojCdkwDCQ3/i79zaKQa9PO5l/dBERRZnmP0SFhPOv24= -github.com/trufnetwork/kwil-db/core v0.4.3-0.20250905175054-602e824e33c2/go.mod h1:HnOsh9+BN13LJCjiH0+XKaJzyjWKf+H9AofFFp90KwQ= +github.com/trufnetwork/kwil-db v0.10.3-0.20250911225741-d6cb2b2747ff h1:TeVummmhXdwNAuRDst/aWQPfvHZh3TUl7Nw6Q2Oogao= +github.com/trufnetwork/kwil-db v0.10.3-0.20250911225741-d6cb2b2747ff/go.mod h1:LiBAC48uZl2B0IiLtD2hpOce7RNfpuDdghVAOc3u1Qo= +github.com/trufnetwork/kwil-db/core v0.4.3-0.20250911225741-d6cb2b2747ff h1:2hQ3ChOBM76eh10Ix0GItIfK5HNwfqrGWhoiQIH/P60= +github.com/trufnetwork/kwil-db/core v0.4.3-0.20250911225741-d6cb2b2747ff/go.mod h1:HnOsh9+BN13LJCjiH0+XKaJzyjWKf+H9AofFFp90KwQ= github.com/trufnetwork/openzeppelin-merkle-tree-go v0.0.2 h1:DCq8MzbWH0wZmICNmMVsSzUHUPl+2vqRhluEABjxl88= github.com/trufnetwork/openzeppelin-merkle-tree-go v0.0.2/go.mod h1:Y0MJpPp9QXU5vC6Gpoilql2NkgmGNcbHm9HYC2v2N8s= github.com/trufnetwork/sdk-go v0.3.2-0.20250630062504-841b40cdb709 h1:d9EqPXIjbq/atzEncK5dM3Z9oStx1BxCGuL/sjefeCw= diff --git a/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go index c19642c10..1f2ba16c5 100644 --- a/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go +++ b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go @@ -38,7 +38,6 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { var chainResult, escrowResult, periodResult string var syncedResult bool r, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "info", []any{}, func(row *common.Row) error { - chainResult = row.Values[0].(string) escrowResult = row.Values[1].(string) periodResult = row.Values[2].(string) diff --git a/tests/extensions/erc20/erc20_bridge_injection_test.go b/tests/extensions/erc20/erc20_bridge_injection_test.go index 387750dd3..afa5e45d5 100644 --- a/tests/extensions/erc20/erc20_bridge_injection_test.go +++ b/tests/extensions/erc20/erc20_bridge_injection_test.go @@ -29,6 +29,11 @@ func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, chain, escrow, erc20, 18, 60, TestChain) require.NoError(t, err) + // Cleanup: Deactivate the test instance + t.Cleanup(func() { + erc20shim.ForTestingDisableInstance(ctx, platform, chain, escrow, TestChain) + }) + // Inject a transfer: from user to escrow (lock/credit path) err = testerc20.InjectERC20Transfer(ctx, platform, chain, escrow, erc20, user, escrow, value, 1, nil) require.NoError(t, err) @@ -58,9 +63,6 @@ func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { require.Equal(t, value, got, "expected balance to reflect injected transfer amount") - // Cleanup: Deactivate the test instance - erc20shim.ForTestingDisableInstance(ctx, platform, chain, escrow, TestChain) - return nil }) } diff --git a/tests/extensions/erc20/erc20_bridge_multi_instance_test.go b/tests/extensions/erc20/erc20_bridge_multi_instance_test.go index 7a2c02704..a4783116a 100644 --- a/tests/extensions/erc20/erc20_bridge_multi_instance_test.go +++ b/tests/extensions/erc20/erc20_bridge_multi_instance_test.go @@ -52,7 +52,9 @@ func TestERC20BridgeMultiInstanceIsolation(t *testing.T) { if len(row.Values) != 1 { return nil } - balanceA, _ = row.Values[0].(*types.Decimal) + if val, ok := row.Values[0].(*types.Decimal); ok { + balanceA = val + } return nil }) require.NoError(t, err) @@ -60,6 +62,7 @@ func TestERC20BridgeMultiInstanceIsolation(t *testing.T) { return r.Error } + require.NotNil(t, balanceA) require.Equal(t, TestAmount1, balanceA.String(), "aliasA should have the deposit amount") // Check balance in aliasB (should be zero - no deposit made) @@ -68,7 +71,9 @@ func TestERC20BridgeMultiInstanceIsolation(t *testing.T) { if len(row.Values) != 1 { return nil } - balanceB, _ = row.Values[0].(*types.Decimal) + if val, ok := row.Values[0].(*types.Decimal); ok { + balanceB = val + } return nil }) require.NoError(t, err) @@ -76,11 +81,14 @@ func TestERC20BridgeMultiInstanceIsolation(t *testing.T) { return r.Error } + require.NotNil(t, balanceB) require.Equal(t, "0", balanceB.String(), "aliasB should have zero balance (no deposit made)") // Cleanup: Deactivate both instances for test isolation - erc20shim.ForTestingDisableInstance(ctx, platform, TestChain, escrowA, aliasA) - erc20shim.ForTestingDisableInstance(ctx, platform, TestChain, escrowB, aliasB) + t.Cleanup(func() { + erc20shim.ForTestingDisableInstance(ctx, platform, TestChain, escrowA, aliasA) + erc20shim.ForTestingDisableInstance(ctx, platform, TestChain, escrowB, aliasB) + }) return nil }) diff --git a/tests/extensions/erc20/erc20_bridge_transfer_test.go b/tests/extensions/erc20/erc20_bridge_transfer_test.go index 1aecac85d..9ba939d18 100644 --- a/tests/extensions/erc20/erc20_bridge_transfer_test.go +++ b/tests/extensions/erc20/erc20_bridge_transfer_test.go @@ -51,10 +51,13 @@ func TestERC20BridgeTransferBalances(t *testing.T) { halfDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) require.NoError(t, err) - _, err = platform.Engine.Call(engineCtx, platform.DB, TestChain, "transfer", []any{TestUserB, halfDec}, func(row *common.Row) error { + r, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "transfer", []any{TestUserB, halfDec}, func(row *common.Row) error { return nil }) require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } // Step 3: Verify balances after transfer // userA should have remaining amount diff --git a/tests/streams/complex_composed_test.go b/tests/streams/complex_composed_test.go index 8831e0c9f..a844a17ab 100644 --- a/tests/streams/complex_composed_test.go +++ b/tests/streams/complex_composed_test.go @@ -52,16 +52,16 @@ func TestComplexComposed(t *testing.T) { func wrapTestWithCacheModes(t *testing.T, testName string, testFunc func(*testing.T, bool) func(ctx context.Context, platform *kwilTesting.Platform, helper *testutils.CacheTestHelper) error) func(ctx context.Context, platform *kwilTesting.Platform, helper *testutils.CacheTestHelper) error { return func(ctx context.Context, platform *kwilTesting.Platform, helper *testutils.CacheTestHelper) error { t.Run(testName+"_without_cache", func(t *testing.T) { - helper.RunInTx(t, func(t cache.TestingT, txPlatform *kwilTesting.Platform) { - err := testFunc(t.(*testing.T), false)(ctx, txPlatform, helper) + helper.RunInTx(t, func(_ cache.TestingT, txPlatform *kwilTesting.Platform) { + err := testFunc(t, false)(ctx, txPlatform, helper) if err != nil { t.Fatalf("Test failed without cache: %v", err) } }) }) t.Run(testName+"_with_cache", func(t *testing.T) { - helper.RunInTx(t, func(t cache.TestingT, txPlatform *kwilTesting.Platform) { - err := testFunc(t.(*testing.T), true)(ctx, txPlatform, helper) + helper.RunInTx(t, func(_ cache.TestingT, txPlatform *kwilTesting.Platform) { + err := testFunc(t, true)(ctx, txPlatform, helper) if err != nil { t.Fatalf("Test failed with cache: %v", err) } diff --git a/tests/streams/utils/cache/helper.go b/tests/streams/utils/cache/helper.go index ee307b5f2..fdf7f3a2b 100644 --- a/tests/streams/utils/cache/helper.go +++ b/tests/streams/utils/cache/helper.go @@ -53,6 +53,9 @@ func SetupCacheTest(ctx context.Context, platform *kwilTesting.Platform, cacheCo // Cleanup cleans up test injection func (h *CacheTestHelper) Cleanup() { + // Clear all in-memory overrides to avoid cross-test leakage + tn_cache.SetTestConfiguration(nil) + tn_cache.SetTestDBConfiguration(config.DBConfig{}) tn_cache.SetTestDB(nil) } diff --git a/tests/streams/utils/erc20/config.go b/tests/streams/utils/erc20/config.go index 45b2cb2b8..1ad8c67e2 100644 --- a/tests/streams/utils/erc20/config.go +++ b/tests/streams/utils/erc20/config.go @@ -5,7 +5,7 @@ import ( "fmt" "net/url" "os" - "strings" + "strconv" "time" "github.com/trufnetwork/kwil-db/config" @@ -127,7 +127,7 @@ func (c *ERC20BridgeConfig) Validate() error { } // Skip validation for mock paths - if keyPath == "/dev/null" { + if keyPath == os.DevNull { continue } @@ -146,11 +146,9 @@ func (c *ERC20BridgeConfig) Validate() error { for chain, chainConfig := range c.Chains { if chainConfig.BlockSyncChunkSize != "" { // Validate chunk size format (should be a number) - if !strings.Contains(chainConfig.BlockSyncChunkSize, ".") { - // If it's an integer, try to parse it - if _, err := url.Parse(chainConfig.BlockSyncChunkSize); err == nil { - continue // It's a valid URL-like string - } + if _, err := strconv.ParseUint(chainConfig.BlockSyncChunkSize, 10, 64); err != nil { + return fmt.Errorf("invalid block sync chunk size for chain %s: %q; must be a positive integer: %v", + chain, chainConfig.BlockSyncChunkSize, err) } } diff --git a/tests/streams/utils/erc20/helper.go b/tests/streams/utils/erc20/helper.go index 4c0f80593..5d061cf0f 100644 --- a/tests/streams/utils/erc20/helper.go +++ b/tests/streams/utils/erc20/helper.go @@ -1,3 +1,5 @@ +//go:build kwiltest + // Package erc20 provides helper functions for ERC-20 bridge testing. // Follows the same patterns as query_test.go for consistency and compatibility. package erc20 @@ -6,7 +8,6 @@ import ( "context" "fmt" "math/big" - "strconv" "testing" "github.com/trufnetwork/kwil-db/common" @@ -78,13 +79,12 @@ func CallLockAdmin(ctx context.Context, platform *kwilTesting.Platform, extensio TxID: platform.Txid(), } engCtx := &common.EngineContext{TxContext: txCtx, OverrideAuthz: true} - - // Convert amount string to Decimal - amt, err := strconv.ParseInt(amount, 10, 64) - if err != nil { - return fmt.Errorf("failed to parse amount: %w", err) + // Convert amount string to Decimal using big.Int + bn := new(big.Int) + if _, ok := bn.SetString(amount, 10); !ok { + return fmt.Errorf("failed to parse amount: %s", amount) } - amtDec, err := types.NewDecimalFromBigInt(big.NewInt(amt), 0) + amtDec, err := types.NewDecimalFromBigInt(bn, 0) if err != nil { return fmt.Errorf("failed to create decimal: %w", err) } diff --git a/tests/streams/utils/erc20/inject.go b/tests/streams/utils/erc20/inject.go index 905ec90d1..94d6d5938 100644 --- a/tests/streams/utils/erc20/inject.go +++ b/tests/streams/utils/erc20/inject.go @@ -1,3 +1,5 @@ +//go:build kwiltest + package erc20 import ( diff --git a/tests/streams/utils/eventstore/mock.go b/tests/streams/utils/eventstore/mock.go index 20d06865b..9a962f440 100644 --- a/tests/streams/utils/eventstore/mock.go +++ b/tests/streams/utils/eventstore/mock.go @@ -4,6 +4,7 @@ package eventstore import ( "context" "fmt" + "sync" "time" "github.com/trufnetwork/kwil-db/extensions/listeners" @@ -11,7 +12,8 @@ import ( // mockEventStore implements listeners.EventStore for testing type mockEventStore struct { - events chan []byte + mu sync.RWMutex + events chan broadcastEvent kvStore map[string][]byte broadcast []broadcastEvent } @@ -25,7 +27,7 @@ type broadcastEvent struct { // NewMockEventStore creates a new mock event store for testing func NewMockEventStore() listeners.EventStore { return &mockEventStore{ - events: make(chan []byte, 100), + events: make(chan broadcastEvent, 100), kvStore: make(map[string][]byte), broadcast: make([]broadcastEvent, 0), } @@ -33,40 +35,48 @@ func NewMockEventStore() listeners.EventStore { // Broadcast implements listeners.EventStore func (m *mockEventStore) Broadcast(ctx context.Context, eventType string, data []byte) error { - m.broadcast = append(m.broadcast, broadcastEvent{ + ev := broadcastEvent{ eventType: eventType, - data: data, + data: append([]byte(nil), data...), timestamp: time.Now(), - }) + } + m.mu.Lock() + m.broadcast = append(m.broadcast, ev) + m.mu.Unlock() // Also send to events channel for waiting select { - case m.events <- data: + case m.events <- ev: default: // Channel full, skip } - return nil } // Set implements listeners.EventKV func (m *mockEventStore) Set(ctx context.Context, key []byte, value []byte) error { - m.kvStore[string(key)] = value + m.mu.Lock() + m.kvStore[string(key)] = append([]byte(nil), value...) + m.mu.Unlock() return nil } // Get implements listeners.EventKV func (m *mockEventStore) Get(ctx context.Context, key []byte) ([]byte, error) { + m.mu.RLock() value, exists := m.kvStore[string(key)] + m.mu.RUnlock() if !exists { return nil, fmt.Errorf("key not found") } - return value, nil + return append([]byte(nil), value...), nil } // Delete implements listeners.EventKV func (m *mockEventStore) Delete(ctx context.Context, key []byte) error { + m.mu.Lock() delete(m.kvStore, string(key)) + m.mu.Unlock() return nil } @@ -75,25 +85,35 @@ func (m *mockEventStore) WaitForEvent(eventType string, timeout time.Duration) e timeoutCtx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() + // Check backlog first + m.mu.RLock() + for _, event := range m.broadcast { + if event.eventType == eventType { + m.mu.RUnlock() + return nil + } + } + m.mu.RUnlock() + for { select { case <-timeoutCtx.Done(): return fmt.Errorf("timeout waiting for event %s", eventType) - default: - // Check if event was already broadcast - for _, event := range m.broadcast { - if event.eventType == eventType { - return nil - } + case ev := <-m.events: + if ev.eventType == eventType { + return nil } - time.Sleep(10 * time.Millisecond) } } } // GetBroadcastEvents returns all broadcast events func (m *mockEventStore) GetBroadcastEvents() []broadcastEvent { - return m.broadcast + m.mu.RLock() + defer m.mu.RUnlock() + out := make([]broadcastEvent, len(m.broadcast)) + copy(out, m.broadcast) + return out } // MockEventStore is a type alias for backward compatibility diff --git a/tests/streams/utils/runner.go b/tests/streams/utils/runner.go index b770981b8..9759c849d 100644 --- a/tests/streams/utils/runner.go +++ b/tests/streams/utils/runner.go @@ -91,6 +91,13 @@ func wrapWithExtensionsSetup(ctx context.Context, originalFuncs []kwilTesting.Te wrapped[i] = func(ctx context.Context, platform *kwilTesting.Platform) (testErr error) { // Collect cleanup functions to run after the test completes var cleanups []func() + defer func() { + for i := len(cleanups) - 1; i >= 0; i-- { + if cleanups[i] != nil { + cleanups[i]() + } + } + }() // Setup cache extension if configured if cacheConfig != nil && cacheConfig.IsEnabled() { @@ -110,13 +117,6 @@ func wrapWithExtensionsSetup(ctx context.Context, originalFuncs []kwilTesting.Te testErr = err } - // Run cleanups in reverse order - for i := len(cleanups) - 1; i >= 0; i-- { - if cleanups[i] != nil { - cleanups[i]() - } - } - return } } From f9950ae445419dbb09aa6acbd983fb55bdbcff39 Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Thu, 11 Sep 2025 22:52:31 -0300 Subject: [PATCH 12/18] docs(migrations): add comment to bootstrap_erc20.sql for test clarity This commit adds a comment to the `bootstrap_erc20.sql` file, clarifying its purpose in manually enabling the ERC20 bootstrap process for testing. This enhancement aims to improve the understanding of the file's role within the test harness. --- internal/migrations/test_only/bootstrap_erc20.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/migrations/test_only/bootstrap_erc20.sql b/internal/migrations/test_only/bootstrap_erc20.sql index dddae2265..b9717ebbb 100644 --- a/internal/migrations/test_only/bootstrap_erc20.sql +++ b/internal/migrations/test_only/bootstrap_erc20.sql @@ -1,3 +1,5 @@ +-- This file exists to manually enable the ERC20 bootstrap process for our tests + USE kwil_erc20_meta as kwil_erc20_meta; -- ordered-sync is provisioned by its genesis hook in node runtime. The test harness From d6e74dd1832df945dc38da75ba4b46917760b404 Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Fri, 12 Sep 2025 09:18:12 -0300 Subject: [PATCH 13/18] refactor(tests): unify ERC-20 bridge test instance activation with extension alias This commit refactors multiple ERC-20 bridge test files to replace direct references to the `TestChain` constant with the new `TestExtensionAlias`. Key changes include: - Updated instance activation calls in various test files to ensure consistent use of the extension alias. - Enhanced clarity and maintainability of the test suite by standardizing the way instances are activated across tests. These adjustments aim to improve the robustness of the ERC-20 bridge testing framework and facilitate easier future enhancements. --- tests/extensions/erc20/common_test.go | 17 ++++++------- .../erc20/erc20_bridge_actions_smoke_test.go | 10 ++++---- .../erc20/erc20_bridge_admin_authz_test.go | 12 +++++----- .../erc20/erc20_bridge_consecutive_test.go | 8 +++---- .../erc20/erc20_bridge_end_to_end_test.go | 12 +++++----- .../erc20/erc20_bridge_epoch_test.go | 4 ++-- .../erc20/erc20_bridge_injection_test.go | 6 ++--- .../erc20/erc20_bridge_simple_test.go | 10 ++++---- .../erc20/erc20_bridge_transfer_test.go | 12 +++++----- tests/streams/utils/erc20/config.go | 24 +++++++++++++++++-- tests/streams/utils/erc20/helper.go | 6 ++--- tests/streams/utils/runner.go | 6 +++-- 12 files changed, 75 insertions(+), 52 deletions(-) diff --git a/tests/extensions/erc20/common_test.go b/tests/extensions/erc20/common_test.go index 4d1997781..ba34f1333 100644 --- a/tests/extensions/erc20/common_test.go +++ b/tests/extensions/erc20/common_test.go @@ -17,14 +17,15 @@ import ( // Common deterministic values used across ERC20 bridge tests const ( - TestChain = "sepolia" - TestEscrowA = "0x1111111111111111111111111111111111111111" - TestEscrowB = "0x2222222222222222222222222222222222222222" - TestERC20 = "0x2222222222222222222222222222222222222222" - TestUserA = "0xabc0000000000000000000000000000000000001" - TestUserB = "0xabc0000000000000000000000000000000000002" - TestAmount1 = "1000000000000000000" // 1.0 tokens - TestAmount2 = "2000000000000000000" // 2.0 tokens + TestChain = "sepolia" + TestExtensionAlias = "erc20_bridge_test" + TestEscrowA = "0x1111111111111111111111111111111111111111" + TestEscrowB = "0x2222222222222222222222222222222222222222" + TestERC20 = "0x2222222222222222222222222222222222222222" + TestUserA = "0xabc0000000000000000000000000000000000001" + TestUserB = "0xabc0000000000000000000000000000000000002" + TestAmount1 = "1000000000000000000" // 1.0 tokens + TestAmount2 = "2000000000000000000" // 2.0 tokens ) // seedAndRun is a helper that handles test execution with proper isolation diff --git a/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go index 1f2ba16c5..a24120497 100644 --- a/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go +++ b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go @@ -29,7 +29,7 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { // Singleton reset and initialize is handled by seedAndRun // Ensure active+synced in-memory - err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestExtensionAlias) require.NoError(t, err) // Test erc20.info() action @@ -37,7 +37,7 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { var chainResult, escrowResult, periodResult string var syncedResult bool - r, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "info", []any{}, func(row *common.Row) error { + r, err := platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "info", []any{}, func(row *common.Row) error { chainResult = row.Values[0].(string) escrowResult = row.Values[1].(string) periodResult = row.Values[2].(string) @@ -57,7 +57,7 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { // Test erc20.decimals() action var decimalsResult int64 - r, err = platform.Engine.Call(engineCtx, platform.DB, TestChain, "decimals", []any{}, func(row *common.Row) error { + r, err = platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "decimals", []any{}, func(row *common.Row) error { if len(row.Values) != 1 { return nil } @@ -73,7 +73,7 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { // Test erc20.scale_up("1") action var scaledUpResult *types.Decimal - r, err = platform.Engine.Call(engineCtx, platform.DB, TestChain, "scale_up", []any{"1"}, func(row *common.Row) error { + r, err = platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "scale_up", []any{"1"}, func(row *common.Row) error { if len(row.Values) != 1 { return nil } @@ -94,7 +94,7 @@ func TestERC20BridgeActionsSmoke(t *testing.T) { amountDecimal, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) require.NoError(t, err) - r, err = platform.Engine.Call(engineCtx, platform.DB, TestChain, "scale_down", []any{amountDecimal}, func(row *common.Row) error { + r, err = platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "scale_down", []any{amountDecimal}, func(row *common.Row) error { if len(row.Values) != 1 { return nil } diff --git a/tests/extensions/erc20/erc20_bridge_admin_authz_test.go b/tests/extensions/erc20/erc20_bridge_admin_authz_test.go index 55bccdc0c..70d0cf67b 100644 --- a/tests/extensions/erc20/erc20_bridge_admin_authz_test.go +++ b/tests/extensions/erc20/erc20_bridge_admin_authz_test.go @@ -28,7 +28,7 @@ import ( func TestERC20BridgeAdminAuthz(t *testing.T) { seedAndRun(t, "erc20_bridge_admin_authz", func(ctx context.Context, platform *kwilTesting.Platform) error { // Enable instance with alias for admin authz test - err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestExtensionAlias) require.NoError(t, err) // Step 1: Inject deposit so user has balance @@ -36,7 +36,7 @@ func TestERC20BridgeAdminAuthz(t *testing.T) { require.NoError(t, err) // Verify user has the balance - balance, err := testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) + balance, err := testerc20.GetUserBalance(ctx, platform, TestExtensionAlias, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount2, balance, "user should have deposit amount") @@ -47,21 +47,21 @@ func TestERC20BridgeAdminAuthz(t *testing.T) { amtDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) require.NoError(t, err) - _, err = platform.Engine.Call(engineCtxFail, platform.DB, TestChain, "lock_admin", []any{TestUserA, amtDec}, func(row *common.Row) error { + _, err = platform.Engine.Call(engineCtxFail, platform.DB, TestExtensionAlias, "lock_admin", []any{TestUserA, amtDec}, func(row *common.Row) error { return nil }) // Should fail with authz error (SYSTEM method called without OverrideAuthz) require.Error(t, err, "action lock_admin is system-only") // Balance should remain unchanged - balance, err = testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) + balance, err = testerc20.GetUserBalance(ctx, platform, TestExtensionAlias, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount2, balance, "balance should remain unchanged after failed lock_admin") // Step 3: Retry lock_admin WITH OverrideAuthz - should succeed engineCtxSuccess := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 3, true) - r, err := platform.Engine.Call(engineCtxSuccess, platform.DB, TestChain, "lock_admin", []any{TestUserA, amtDec}, func(row *common.Row) error { + r, err := platform.Engine.Call(engineCtxSuccess, platform.DB, TestExtensionAlias, "lock_admin", []any{TestUserA, amtDec}, func(row *common.Row) error { return nil }) require.NoError(t, err) @@ -70,7 +70,7 @@ func TestERC20BridgeAdminAuthz(t *testing.T) { } // Step 4: Verify balance was reduced after successful lock_admin - balance, err = testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) + balance, err = testerc20.GetUserBalance(ctx, platform, TestExtensionAlias, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount1, balance, "balance should be reduced after successful lock_admin") diff --git a/tests/extensions/erc20/erc20_bridge_consecutive_test.go b/tests/extensions/erc20/erc20_bridge_consecutive_test.go index 91ae18086..df03edec3 100644 --- a/tests/extensions/erc20/erc20_bridge_consecutive_test.go +++ b/tests/extensions/erc20/erc20_bridge_consecutive_test.go @@ -20,13 +20,13 @@ func TestERC20BridgeConsecutiveTests(t *testing.T) { t.Run("first_test", func(t *testing.T) { seedAndRun(t, "erc20_bridge_first", func(ctx context.Context, platform *kwilTesting.Platform) error { // Enable instance with alias for first test - err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestExtensionAlias) require.NoError(t, err) // Simple test: just call info() to verify the extension is working engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 1, false) var syncedResult bool - res, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "info", []any{}, func(row *common.Row) error { + res, err := platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "info", []any{}, func(row *common.Row) error { if len(row.Values) >= 7 { syncedResult = row.Values[6].(bool) } @@ -47,7 +47,7 @@ func TestERC20BridgeConsecutiveTests(t *testing.T) { t.Run("second_test", func(t *testing.T) { seedAndRun(t, "erc20_bridge_second", func(ctx context.Context, platform *kwilTesting.Platform) error { // This should not fail with "already active" error due to proper cleanup - err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestExtensionAlias) if err != nil { // If this fails, let's check the DB state to debug var isActive bool @@ -68,7 +68,7 @@ func TestERC20BridgeConsecutiveTests(t *testing.T) { // Simple test: just call info() to verify the extension is working engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 1, false) var syncedResult bool - res, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "info", []any{}, func(row *common.Row) error { + res, err := platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "info", []any{}, func(row *common.Row) error { if len(row.Values) >= 7 { syncedResult = row.Values[6].(bool) } diff --git a/tests/extensions/erc20/erc20_bridge_end_to_end_test.go b/tests/extensions/erc20/erc20_bridge_end_to_end_test.go index 6c2ea5e32..eb91f4569 100644 --- a/tests/extensions/erc20/erc20_bridge_end_to_end_test.go +++ b/tests/extensions/erc20/erc20_bridge_end_to_end_test.go @@ -30,12 +30,12 @@ import ( func TestERC20BridgeEndToEnd(t *testing.T) { seedAndRun(t, "erc20_bridge_end_to_end", func(ctx context.Context, platform *kwilTesting.Platform) error { // Enable instance with alias for end-to-end test - require.NoError(t, erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 1, TestChain)) + require.NoError(t, erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 1, TestExtensionAlias)) // Sanity: ensure the instance reports synced and enabled via info() engineCtx := engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 1, false) var syncedResult, enabledResult bool - resInfo, errInfo := platform.Engine.Call(engineCtx, platform.DB, TestChain, "info", []any{}, func(row *common.Row) error { + resInfo, errInfo := platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "info", []any{}, func(row *common.Row) error { if len(row.Values) < 9 { return nil } @@ -55,7 +55,7 @@ func TestERC20BridgeEndToEnd(t *testing.T) { require.NoError(t, err) // Verify user has the balance - balance, err := testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) + balance, err := testerc20.GetUserBalance(ctx, platform, TestExtensionAlias, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount1, balance, "user should have deposit amount") @@ -64,7 +64,7 @@ func TestERC20BridgeEndToEnd(t *testing.T) { amtDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) require.NoError(t, err) - r, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "bridge", []any{amtDec}, func(row *common.Row) error { + r, err := platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "bridge", []any{amtDec}, func(row *common.Row) error { return nil }) require.NoError(t, err) @@ -98,7 +98,7 @@ func TestERC20BridgeEndToEnd(t *testing.T) { // Diagnostics: fetch instance id via alias engineCtx = engCtx(ctx, platform, "0x0000000000000000000000000000000000000000", 3, false) var instanceID *types.UUID - resID, errID := platform.Engine.Call(engineCtx, platform.DB, TestChain, "id", []any{}, func(row *common.Row) error { + resID, errID := platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "id", []any{}, func(row *common.Row) error { if len(row.Values) != 1 { return nil } @@ -113,7 +113,7 @@ func TestERC20BridgeEndToEnd(t *testing.T) { // Step 5: Query wallet rewards (confirmed only) to verify bridge flow worked deterministically rewardRows := 0 - r, err = platform.Engine.Call(engineCtx, platform.DB, TestChain, "list_wallet_rewards", []any{TestUserA, false}, func(row *common.Row) error { + r, err = platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "list_wallet_rewards", []any{TestUserA, false}, func(row *common.Row) error { rewardRows++ return nil }) diff --git a/tests/extensions/erc20/erc20_bridge_epoch_test.go b/tests/extensions/erc20/erc20_bridge_epoch_test.go index c6e13548b..94f17f85b 100644 --- a/tests/extensions/erc20/erc20_bridge_epoch_test.go +++ b/tests/extensions/erc20/erc20_bridge_epoch_test.go @@ -28,7 +28,7 @@ func TestERC20BridgeEpochFlow(t *testing.T) { value := "500000000000000000" // 0.5 // Enable instance with alias (includes alias creation, activation via period + rehydrate), idempotently and collision-free - require.NoError(t, erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, chain, escrow, erc20, 18, 1, TestChain)) + require.NoError(t, erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, chain, escrow, erc20, 18, 1, TestExtensionAlias)) // Credit balance via injected transfer (simulates inbound deposit) require.NoError(t, testerc20.InjectERC20Transfer(ctx, platform, chain, escrow, erc20, user, escrow, value, 10, nil)) @@ -70,7 +70,7 @@ func TestERC20BridgeEpochFlow(t *testing.T) { require.Greater(t, rows, 0, "expected confirmed wallet reward rows") // Cleanup: Disable the test instance (tears down runtimes + resets singleton) - require.NoError(t, erc20shim.ForTestingDisableInstance(ctx, platform, chain, escrow, TestChain)) + require.NoError(t, erc20shim.ForTestingDisableInstance(ctx, platform, chain, escrow, TestExtensionAlias)) return nil }) diff --git a/tests/extensions/erc20/erc20_bridge_injection_test.go b/tests/extensions/erc20/erc20_bridge_injection_test.go index afa5e45d5..f22d4962c 100644 --- a/tests/extensions/erc20/erc20_bridge_injection_test.go +++ b/tests/extensions/erc20/erc20_bridge_injection_test.go @@ -26,12 +26,12 @@ func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { value := "1000000000000000000" // Enable instance with alias for injection test - err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, chain, escrow, erc20, 18, 60, TestChain) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, chain, escrow, erc20, 18, 60, TestExtensionAlias) require.NoError(t, err) // Cleanup: Deactivate the test instance t.Cleanup(func() { - erc20shim.ForTestingDisableInstance(ctx, platform, chain, escrow, TestChain) + erc20shim.ForTestingDisableInstance(ctx, platform, chain, escrow, TestExtensionAlias) }) // Inject a transfer: from user to escrow (lock/credit path) @@ -49,7 +49,7 @@ func TestERC20BridgeInjectedTransferAffectsBalance(t *testing.T) { engCtx := &common.EngineContext{TxContext: txCtx} var got string - r, err := platform.Engine.Call(engCtx, platform.DB, TestChain, "balance", []any{user}, func(row *common.Row) error { + r, err := platform.Engine.Call(engCtx, platform.DB, TestExtensionAlias, "balance", []any{user}, func(row *common.Row) error { if len(row.Values) != 1 { return fmt.Errorf("expected 1 column, got %d", len(row.Values)) } diff --git a/tests/extensions/erc20/erc20_bridge_simple_test.go b/tests/extensions/erc20/erc20_bridge_simple_test.go index f5cf17e0b..8869105e4 100644 --- a/tests/extensions/erc20/erc20_bridge_simple_test.go +++ b/tests/extensions/erc20/erc20_bridge_simple_test.go @@ -18,7 +18,7 @@ import ( func TestERC20BridgeSimpleBalanceTx(t *testing.T) { seedAndRun(t, "erc20_bridge_simple_balance_tx", func(ctx context.Context, platform *kwilTesting.Platform) error { // Enable instance with alias in one step - err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestExtensionAlias) require.NoError(t, err) // Run the actual test logic @@ -33,7 +33,7 @@ func testSimpleBalanceTx(t *testing.T, txPlatform *kwilTesting.Platform) { const testWallet = "0x1111111111111111111111111111111111110001" // Query balance for a wallet with no prior deposits - balance, err := testerc20.GetUserBalance(context.Background(), txPlatform, TestChain, testWallet) + balance, err := testerc20.GetUserBalance(context.Background(), txPlatform, TestExtensionAlias, testWallet) require.NoError(t, err) // Should return "0" for wallet with no prior deposits @@ -59,7 +59,7 @@ func testSimpleBalanceTx(t *testing.T, txPlatform *kwilTesting.Platform) { func TestERC20BridgeAdminLockAffectsBalanceTx(t *testing.T) { seedAndRun(t, "erc20_bridge_admin_lock_affects_balance_tx", func(ctx context.Context, platform *kwilTesting.Platform) error { // Enable instance with alias in one step - err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestExtensionAlias) require.NoError(t, err) // Run the actual test logic @@ -84,12 +84,12 @@ func testAdminLockTx(t *testing.T, txPlatform *kwilTesting.Platform) { // Step 2: Execute admin lock operation // This should reduce user's balance by the locked amount - err = testerc20.CallLockAdmin(context.Background(), txPlatform, TestChain, testUser, testAmount) + err = testerc20.CallLockAdmin(context.Background(), txPlatform, TestExtensionAlias, testUser, testAmount) require.NoError(t, err) // Step 3: Verify balance was correctly reduced // After locking all tokens, balance should be 0 - finalBalance, err := testerc20.GetUserBalance(context.Background(), txPlatform, TestChain, testUser) + finalBalance, err := testerc20.GetUserBalance(context.Background(), txPlatform, TestExtensionAlias, testUser) require.NoError(t, err) require.Equal(t, "0", finalBalance, "expected user balance to be zero after admin lock of entire balance") diff --git a/tests/extensions/erc20/erc20_bridge_transfer_test.go b/tests/extensions/erc20/erc20_bridge_transfer_test.go index 9ba939d18..d771c88ae 100644 --- a/tests/extensions/erc20/erc20_bridge_transfer_test.go +++ b/tests/extensions/erc20/erc20_bridge_transfer_test.go @@ -27,7 +27,7 @@ import ( func TestERC20BridgeTransferBalances(t *testing.T) { seedAndRun(t, "erc20_bridge_transfer_balances", func(ctx context.Context, platform *kwilTesting.Platform) error { // Enable instance with alias for transfer test - err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestChain) + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestExtensionAlias) require.NoError(t, err) // Step 1: Inject deposit for userA @@ -35,12 +35,12 @@ func TestERC20BridgeTransferBalances(t *testing.T) { require.NoError(t, err) // Verify userA received the full deposit - balanceA, err := testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) + balanceA, err := testerc20.GetUserBalance(ctx, platform, TestExtensionAlias, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount2, balanceA, "userA should have full deposit amount") // Verify userB has zero balance initially - balanceB, err := testerc20.GetUserBalance(ctx, platform, TestChain, TestUserB) + balanceB, err := testerc20.GetUserBalance(ctx, platform, TestExtensionAlias, TestUserB) require.NoError(t, err) require.Equal(t, "0", balanceB, "userB should have zero balance initially") @@ -51,7 +51,7 @@ func TestERC20BridgeTransferBalances(t *testing.T) { halfDec, err := types.ParseDecimalExplicit(TestAmount1, 78, 0) require.NoError(t, err) - r, err := platform.Engine.Call(engineCtx, platform.DB, TestChain, "transfer", []any{TestUserB, halfDec}, func(row *common.Row) error { + r, err := platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "transfer", []any{TestUserB, halfDec}, func(row *common.Row) error { return nil }) require.NoError(t, err) @@ -61,12 +61,12 @@ func TestERC20BridgeTransferBalances(t *testing.T) { // Step 3: Verify balances after transfer // userA should have remaining amount - balanceA, err = testerc20.GetUserBalance(ctx, platform, TestChain, TestUserA) + balanceA, err = testerc20.GetUserBalance(ctx, platform, TestExtensionAlias, TestUserA) require.NoError(t, err) require.Equal(t, TestAmount1, balanceA, "userA should have remaining amount after transfer") // userB should have received the transferred amount - balanceB, err = testerc20.GetUserBalance(ctx, platform, TestChain, TestUserB) + balanceB, err = testerc20.GetUserBalance(ctx, platform, TestExtensionAlias, TestUserB) require.NoError(t, err) require.Equal(t, TestAmount1, balanceB, "userB should have received transferred amount") diff --git a/tests/streams/utils/erc20/config.go b/tests/streams/utils/erc20/config.go index 1ad8c67e2..9ac5ee0db 100644 --- a/tests/streams/utils/erc20/config.go +++ b/tests/streams/utils/erc20/config.go @@ -77,7 +77,23 @@ func (c *ERC20BridgeConfig) WithMockListener(name string, listener listeners.Lis return c } -// BuildERC20BridgeConfig converts the test config to kwil-db config format +// BuildERC20BridgeConfig converts the test config to kwil-db config format. +// +// Note: +// Only the fields supported by kwil-db's config are propagated here: +// - RPC +// - BlockSyncChuckSize +// - Signer +// +// The following fields are intentionally test-only and are not copied into +// kwil-db's config.ERC20BridgeConfig because it does not expose them: +// - Timeout +// - AutoStart +// - MockListeners +// - Per-chain retry settings: MaxRetries, ReconnectionInterval +// +// If kwil-db adds typed support for any of the above, propagate them here and +// plumb them through the listeners' initialization accordingly. func (c *ERC20BridgeConfig) BuildERC20BridgeConfig() *config.ERC20BridgeConfig { erc20Config := &config.ERC20BridgeConfig{ RPC: c.RPC, @@ -146,9 +162,13 @@ func (c *ERC20BridgeConfig) Validate() error { for chain, chainConfig := range c.Chains { if chainConfig.BlockSyncChunkSize != "" { // Validate chunk size format (should be a number) - if _, err := strconv.ParseUint(chainConfig.BlockSyncChunkSize, 10, 64); err != nil { + v, err := strconv.ParseUint(chainConfig.BlockSyncChunkSize, 10, 64) + if err != nil { return fmt.Errorf("invalid block sync chunk size for chain %s: %q; must be a positive integer: %v", chain, chainConfig.BlockSyncChunkSize, err) + } else if v == 0 { + return fmt.Errorf("invalid block sync chunk size for chain %s: %q; must be > 0", + chain, chainConfig.BlockSyncChunkSize) } } diff --git a/tests/streams/utils/erc20/helper.go b/tests/streams/utils/erc20/helper.go index 5d061cf0f..450ce1997 100644 --- a/tests/streams/utils/erc20/helper.go +++ b/tests/streams/utils/erc20/helper.go @@ -17,18 +17,18 @@ import ( ) // WithERC20TestSetupTx is a transaction-aware version that works with WithTx for perfect isolation -func WithERC20TestSetup(alias string, escrowAddr string) func(t *testing.T, txPlatform *kwilTesting.Platform) { +func WithERC20TestSetup(chain, alias string, escrowAddr string) func(t *testing.T, txPlatform *kwilTesting.Platform) { return func(t *testing.T, txPlatform *kwilTesting.Platform) { // Use the transaction-scoped platform // Step 1: Set up cleanup to deactivate the instance after the test // This ensures the next test can reuse the same escrow address t.Cleanup(func() { - erc20shim.ForTestingDisableInstance(context.Background(), txPlatform, alias, escrowAddr, alias) + erc20shim.ForTestingDisableInstance(context.Background(), txPlatform, chain, escrowAddr, alias) erc20shim.ForTestingResetSingleton() }) - erc20shim.ForTestingSeedAndActivateInstance(context.Background(), txPlatform, alias, escrowAddr, "0x2222222222222222222222222222222222222222", 18, 60, alias) + erc20shim.ForTestingSeedAndActivateInstance(context.Background(), txPlatform, chain, escrowAddr, "0x2222222222222222222222222222222222222222", 18, 60, alias) } } diff --git a/tests/streams/utils/runner.go b/tests/streams/utils/runner.go index 9759c849d..b14b0982d 100644 --- a/tests/streams/utils/runner.go +++ b/tests/streams/utils/runner.go @@ -134,11 +134,13 @@ func setupCacheExtension(ctx context.Context, cacheConfig *cache.CacheOptions, p // Parse and setup extension processedConfig, err := tn_cache.ParseConfig(mockService) if err != nil { + helper.Cleanup() return nil, fmt.Errorf("failed to parse cache config: %w", err) } ext, err := tn_cache.SetupCacheExtension(ctx, processedConfig, platform.Engine, mockService) if err != nil { + helper.Cleanup() return nil, fmt.Errorf("failed to setup cache extension: %w", err) } @@ -146,8 +148,6 @@ func setupCacheExtension(ctx context.Context, cacheConfig *cache.CacheOptions, p tn_cache.SetExtension(ext) cleanup := func() { - // Cleanup helper first - helper.Cleanup() // Stop background tasks if ext.Scheduler() != nil { if stopErr := ext.Scheduler().Stop(); stopErr != nil { @@ -159,6 +159,8 @@ func setupCacheExtension(ctx context.Context, cacheConfig *cache.CacheOptions, p } ext.Close() tn_cache.SetExtension(nil) + // Cleanup helper last + helper.Cleanup() } return cleanup, nil From cede83385abb0e54ab8801d4e1a24cd5184f10b5 Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Fri, 12 Sep 2025 09:41:45 -0300 Subject: [PATCH 14/18] fix(tests): improve error handling in ERC-20 test setup and admin lock call This commit enhances the error handling in the ERC-20 test setup by adding a check for the success of the `ForTestingSeedAndActivateInstance` function, ensuring that any activation failures are reported clearly. Additionally, it improves the `CallLockAdmin` function to handle potential execution errors from the engine call, providing more informative error messages. These changes aim to increase the robustness and reliability of the ERC-20 bridge testing framework. --- tests/streams/utils/erc20/helper.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/streams/utils/erc20/helper.go b/tests/streams/utils/erc20/helper.go index 450ce1997..0e8f394da 100644 --- a/tests/streams/utils/erc20/helper.go +++ b/tests/streams/utils/erc20/helper.go @@ -28,7 +28,12 @@ func WithERC20TestSetup(chain, alias string, escrowAddr string) func(t *testing. erc20shim.ForTestingResetSingleton() }) - erc20shim.ForTestingSeedAndActivateInstance(context.Background(), txPlatform, chain, escrowAddr, "0x2222222222222222222222222222222222222222", 18, 60, alias) + if err := erc20shim.ForTestingSeedAndActivateInstance( + context.Background(), txPlatform, chain, escrowAddr, + "0x2222222222222222222222222222222222222222", 18, 60, alias, + ); err != nil { + t.Fatalf("seed/activate ERC20 instance failed: %v", err) + } } } @@ -90,11 +95,14 @@ func CallLockAdmin(ctx context.Context, platform *kwilTesting.Platform, extensio } amtDec.SetPrecisionAndScale(78, 0) - _, err = platform.Engine.Call(engCtx, platform.DB, extensionAlias, "lock_admin", []any{userAddr, amtDec}, func(row *common.Row) error { + r, err := platform.Engine.Call(engCtx, platform.DB, extensionAlias, "lock_admin", []any{userAddr, amtDec}, func(row *common.Row) error { return nil }) if err != nil { return fmt.Errorf("engine call error: %w", err) } + if r != nil && r.Error != nil { + return fmt.Errorf("engine execution error: %w", r.Error) + } return nil } From d148bea540bca9ba2496fd39f93b274402fbd08d Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Fri, 12 Sep 2025 11:01:51 -0300 Subject: [PATCH 15/18] refactor(tests): replace kwilTesting.RunSchemaTest with testutils.RunSchemaTest for cache handling This commit updates various test files to replace instances of `kwilTesting.RunSchemaTest` with `testutils.RunSchemaTest`, ensuring that all tests utilize the new caching mechanism. This change enhances test reliability by ensuring proper cache management and isolation during test execution. Key updates include modifications across multiple test files, improving consistency and maintainability in the testing framework. --- .../internal/engine_ops_integration_test.go | 4 +- .../internal/engine_ops_permissions_test.go | 4 +- .../tn_digest/engine_ops_integration_test.go | 2 +- .../benchmark/digest/digest_benchmark_test.go | 4 +- tests/streams/auth/auth_test.go | 38 +++++++++---------- tests/streams/common_test.go | 16 ++++---- tests/streams/composed_test.go | 4 +- ...rehensive_shared_path_independence_test.go | 4 +- tests/streams/database_size_test.go | 4 +- tests/streams/digest/digest_actions_test.go | 8 ++-- tests/streams/gamefi_index_test.go | 4 +- tests/streams/index_change_test.go | 4 +- tests/streams/multi_level_composed_test.go | 4 +- tests/streams/other/other_test.go | 16 ++++---- .../streams/other/stream_exists_batch_test.go | 4 +- tests/streams/pending_prune_days_test.go | 8 ++-- .../primitive_batch_insert_alignment_test.go | 4 +- tests/streams/primitive_test.go | 4 +- tests/streams/query/metadata_test.go | 4 +- tests/streams/query/query_test.go | 4 +- tests/streams/roles/permission_gates_test.go | 4 +- tests/streams/roles/role_management_test.go | 4 +- tests/streams/taxonomy_query_actions_test.go | 4 +- .../truflation_composed_frozen_test.go | 4 +- .../truflation_primitive_frozen_test.go | 4 +- tests/streams/utils/runner.go | 35 ++++++++++++----- 26 files changed, 107 insertions(+), 92 deletions(-) diff --git a/extensions/tn_cache/internal/engine_ops_integration_test.go b/extensions/tn_cache/internal/engine_ops_integration_test.go index 238c5524c..382ab5904 100644 --- a/extensions/tn_cache/internal/engine_ops_integration_test.go +++ b/extensions/tn_cache/internal/engine_ops_integration_test.go @@ -50,7 +50,7 @@ func testEngineOperationsIntegration(t *testing.T) func(ctx context.Context, pla if err != nil { return errors.Wrap(err, "error registering data provider") } - + logger := log.NewStdoutLogger().New("tn_ops_test") // Test 1: ListComposedStreams @@ -348,7 +348,7 @@ func testEngineOperationsRealTime(t *testing.T) func(ctx context.Context, platfo if err != nil { return errors.Wrap(err, "error registering data provider") } - + logger := log.NewStdoutLogger() // Test fetching recent data diff --git a/extensions/tn_cache/internal/engine_ops_permissions_test.go b/extensions/tn_cache/internal/engine_ops_permissions_test.go index 20f401242..cb55685fb 100644 --- a/extensions/tn_cache/internal/engine_ops_permissions_test.go +++ b/extensions/tn_cache/internal/engine_ops_permissions_test.go @@ -24,13 +24,13 @@ import ( // and private streams for caching purposes. The SQL authorization functions check if // wallet_address = 'extension_agent' and grant unrestricted access. func TestExtensionAgentPermissions(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "extension_agent_permissions_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testExtensionAgentAccess(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testExtensionAgentAccess(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/extensions/tn_digest/engine_ops_integration_test.go b/extensions/tn_digest/engine_ops_integration_test.go index 2897a23d8..03f0dae14 100644 --- a/extensions/tn_digest/engine_ops_integration_test.go +++ b/extensions/tn_digest/engine_ops_integration_test.go @@ -28,7 +28,7 @@ func TestBuildAndBroadcastAutoDigestTx_VerifiesTxBuildSignAndDBEffect(t *testing bts, err := digestembed.TestMigrationSQL.ReadFile("test_migration.sql") require.NoError(t, err) - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "tn_digest_tx_build_broadcast_test", SeedStatements: []string{string(bts)}, FunctionTests: []kwilTesting.TestFunc{ diff --git a/internal/benchmark/digest/digest_benchmark_test.go b/internal/benchmark/digest/digest_benchmark_test.go index 166f38677..e6336ed12 100644 --- a/internal/benchmark/digest/digest_benchmark_test.go +++ b/internal/benchmark/digest/digest_benchmark_test.go @@ -195,13 +195,13 @@ func createBenchmarkSuiteFunc(t *testing.T, scale DigestBenchmarkScale) func(con // runDigestBenchmarkScale runs a specific benchmark scale. // This is the core function that handles all benchmark execution logic. func runDigestBenchmarkScale(t *testing.T, scale DigestBenchmarkScale) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: fmt.Sprintf("digest_benchmark_%s", scale.Name), SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ WithDigestBenchmarkSetup(createBenchmarkSuiteFunc(t, scale)), }, - }, utils.GetTestOptions()) + }, utils.GetTestOptionsWithCache()) } // WithDigestBenchmarkSetup sets up the testing environment for digest benchmarks. diff --git a/tests/streams/auth/auth_test.go b/tests/streams/auth/auth_test.go index e9b5a2458..3bef5a2ad 100644 --- a/tests/streams/auth/auth_test.go +++ b/tests/streams/auth/auth_test.go @@ -42,24 +42,24 @@ var ( func TestAUTH01_StreamOwnership(t *testing.T) { // Test valid ownership transfer t.Run("ValidOwnershipTransfer", func(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "stream_ownership_transfer_AUTH01", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testStreamOwnershipTransfer(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) }) //Test invalid address handling t.Run("InvalidAddressHandling", func(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "invalid_address_ownership_transfer_AUTH01", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testInvalidAddressOwnershipTransfer(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) }) } @@ -174,14 +174,14 @@ func testInvalidAddressOwnershipTransfer(t *testing.T) kwilTesting.TestFunc { // TestAUTH02_ReadPermissions tests AUTH02: A stream owner can control who is allowed to read data from its stream func TestAUTH02_ReadPermissions(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "read_permission_control_AUTH02", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testReadPermissionControl(t, primitiveStreamInfo), testReadPermissionControl(t, composedStreamInfo), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testReadPermissionControl(t *testing.T, streamInfo setup.StreamInfo) kwilTesting.TestFunc { @@ -295,13 +295,13 @@ func expectedVerb(canRead bool) string { // TestAUTH02_NestedReadPermissions tests read permissions across a chain of composed streams func TestAUTH02_NestedReadPermissions(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "nested_read_permission_control_AUTH02", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testNestedReadPermissionControl(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testNestedReadPermissionControl(t *testing.T) kwilTesting.TestFunc { @@ -456,14 +456,14 @@ func testNestedReadPermissionControl(t *testing.T) kwilTesting.TestFunc { // TestAUTH03_WritePermissions tests AUTH03: The stream owner can control which wallets are allowed to insert data into the stream. func TestAUTH03_WritePermissions(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "write_permission_control_AUTH03", FunctionTests: []kwilTesting.TestFunc{ testWritePermissionControl(t, primitiveStreamInfo), testWritePermissionControl(t, composedStreamInfo), }, SeedScripts: migrations.GetSeedScriptPaths(), - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testWritePermissionControl(t *testing.T, streamInfo setup.StreamInfo) kwilTesting.TestFunc { @@ -529,14 +529,14 @@ func testWritePermissionControl(t *testing.T, streamInfo setup.StreamInfo) kwilT // // TestAUTH04_ComposePermissions tests AUTH04: The stream owner can control which streams are allowed to compose from the stream. func TestAUTH04_ComposePermissions(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "compose_permission_control_AUTH04", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testComposePermissionControl(t, primitiveStreamInfo), testComposePermissionControl(t, composedStreamInfo), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testComposePermissionControl(t *testing.T, contractInfo setup.StreamInfo) kwilTesting.TestFunc { @@ -626,13 +626,13 @@ func testComposePermissionControl(t *testing.T, contractInfo setup.StreamInfo) k // TestAUTH04_NestedComposePermissions tests AUTH04: // The stream owner can control which streams (and their nested substreams) are allowed to compose from the stream. func TestAUTH04_NestedComposePermissions(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "nested_compose_permission_control_AUTH04", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testNestedComposePermissionControl(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testNestedComposePermissionControl(t *testing.T) kwilTesting.TestFunc { @@ -844,7 +844,7 @@ func testNestedComposePermissionControl(t *testing.T) kwilTesting.TestFunc { // // TestAUTH05_StreamDeletion tests AUTH05: Stream owners are able to delete their streams and all associated data. // func TestAUTH05_StreamDeletion(t *testing.T) { // t.Skip("Test skipped: auth stream tests temporarily disabled") -// kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ +// testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ // Name: "stream_deletion_AUTH05", // FunctionTests: []kwilTesting.TestFunc{ // testStreamDeletion(t, primitiveContractInfo), @@ -854,13 +854,13 @@ func testNestedComposePermissionControl(t *testing.T) kwilTesting.TestFunc { // } func TestAUTH05_StreamDeletion(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "stream_deletion_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testStreamDeletion(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testStreamDeletion(t *testing.T) kwilTesting.TestFunc { @@ -921,13 +921,13 @@ func testStreamDeletion(t *testing.T) kwilTesting.TestFunc { } func TestFilterStreamsByExistence(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "filter_streams_by_existence_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testFilterStreamsByExistence(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testFilterStreamsByExistence(t *testing.T) kwilTesting.TestFunc { diff --git a/tests/streams/common_test.go b/tests/streams/common_test.go index dfecf507a..1ac5f0827 100644 --- a/tests/streams/common_test.go +++ b/tests/streams/common_test.go @@ -41,14 +41,14 @@ var ( ) func TestCOMMON03DisableMetadata(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "disable_metadata", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testDisableMetadata(t, primitiveStreamInfo), testDisableMetadata(t, composedStreamInfo), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testDisableMetadata(t *testing.T, streamInfo setup.StreamInfo) kwilTesting.TestFunc { @@ -115,14 +115,14 @@ func testDisableMetadata(t *testing.T, streamInfo setup.StreamInfo) kwilTesting. } func TestCOMMON02ReadOnlyMetadataCannotBeModified(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "readonly_metadata_cannot_be_modified", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testReadOnlyMetadataCannotBeModified(t, primitiveStreamInfo), testReadOnlyMetadataCannotBeModified(t, composedStreamInfo), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testReadOnlyMetadataCannotBeModified(t *testing.T, streamInfo setup.StreamInfo) kwilTesting.TestFunc { @@ -175,14 +175,14 @@ func testReadOnlyMetadataCannotBeModified(t *testing.T, streamInfo setup.StreamI } func TestCOMMON02BReadOnlyMetadataCannotBeModified_Batch(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "readonly_metadata_cannot_be_modified_batch", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testReadOnlyMetadataCannotBeModifiedBatch(t, primitiveStreamInfo), testReadOnlyMetadataCannotBeModifiedBatch(t, composedStreamInfo), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testReadOnlyMetadataCannotBeModifiedBatch(t *testing.T, streamInfo setup.StreamInfo) kwilTesting.TestFunc { @@ -237,14 +237,14 @@ func testReadOnlyMetadataCannotBeModifiedBatch(t *testing.T, streamInfo setup.St } func TestVisibilitySettings(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "visibility_settings", FunctionTests: []kwilTesting.TestFunc{ testVisibilitySettings(t, primitiveStreamInfo), testVisibilitySettings(t, composedStreamInfo), }, SeedScripts: migrations.GetSeedScriptPaths(), - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testVisibilitySettings(t *testing.T, streamInfo setup.StreamInfo) kwilTesting.TestFunc { diff --git a/tests/streams/composed_test.go b/tests/streams/composed_test.go index c49f1d948..5743fe690 100644 --- a/tests/streams/composed_test.go +++ b/tests/streams/composed_test.go @@ -24,7 +24,7 @@ var ( ) func TestComposed(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "composed_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -35,7 +35,7 @@ func TestComposed(t *testing.T) { WithComposedTestSetup(testOnlyOwnerCanDisableTaxonomy(t)), WithComposedTestSetup(testCOMPOSED03SetReadOnlyMetadataToComposedStream(t)), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func WithComposedTestSetup(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) kwilTesting.TestFunc { diff --git a/tests/streams/comprehensive_shared_path_independence_test.go b/tests/streams/comprehensive_shared_path_independence_test.go index e8cdb2ae3..49c347c98 100644 --- a/tests/streams/comprehensive_shared_path_independence_test.go +++ b/tests/streams/comprehensive_shared_path_independence_test.go @@ -32,13 +32,13 @@ var ( // TestComprehensivePathIndependenceWithSharedPrimitive tests path independence // for a hierarchy where a single primitive contributes via multiple composed paths. func TestComprehensivePathIndependenceWithSharedPrimitive(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "comprehensive_path_independence_shared_primitive_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ setupComprehensiveSharedStreams(testComprehensivePathIndependenceSharedFunc(t)), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } // setupComprehensiveSharedStreams sets up GCP1, GCP2, SP primitives and CC1, CC2, RC composed streams diff --git a/tests/streams/database_size_test.go b/tests/streams/database_size_test.go index bdc98e43f..7d3dcddfb 100644 --- a/tests/streams/database_size_test.go +++ b/tests/streams/database_size_test.go @@ -23,13 +23,13 @@ var ( ) func TestDatabaseSize(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "database_size_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ WithTestDatabaseSizeSetup(testDatabaseSize(t)), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func WithTestDatabaseSizeSetup(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) kwilTesting.TestFunc { diff --git a/tests/streams/digest/digest_actions_test.go b/tests/streams/digest/digest_actions_test.go index 5edd36216..7275990d5 100644 --- a/tests/streams/digest/digest_actions_test.go +++ b/tests/streams/digest/digest_actions_test.go @@ -32,7 +32,7 @@ var digestTestStreamId = util.GenerateStreamId(digestTestStreamName) var idempotencyTestStreamId = util.GenerateStreamId(idempotencyTestStreamName) func TestDigestActions(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "digest_actions_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -61,12 +61,12 @@ func TestDigestActions(t *testing.T) { WithAutoDigestZeroExpectedSetup(testAutoDigest_ValidatesExpectedRecordsInput(t)), WithSignerAndProvider(testAutoDigest_PreservesRecentDaysCutoff(t)), }, - }, testutils.GetTestOptionsWithCache().Options) + }, testutils.GetTestOptionsWithCache()) } // Verifies leader-only authorization on digest actions using BlockContext.Proposer. func TestDigestActionsLeaderAuthorization(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "digest_actions_leader_authorization", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -130,7 +130,7 @@ func TestDigestActionsLeaderAuthorization(t *testing.T) { return nil }), }, - }, testutils.GetTestOptionsWithCache().Options) + }, testutils.GetTestOptionsWithCache()) } // WithDigestTestSetup sets up test environment with digest-specific data diff --git a/tests/streams/gamefi_index_test.go b/tests/streams/gamefi_index_test.go index 7d03728a0..0e469097e 100644 --- a/tests/streams/gamefi_index_test.go +++ b/tests/streams/gamefi_index_test.go @@ -98,7 +98,7 @@ var ( ) func TestGamefiIndex(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "gamefi_index_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -106,7 +106,7 @@ func TestGamefiIndex(t *testing.T) { WithGamefiProportionalTestSetup(testGamefiIndexProportionalCalculation(t)), testManualCalculationVerification(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func WithGamefiTestSetup(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/tests/streams/index_change_test.go b/tests/streams/index_change_test.go index d328336c6..c03155aa9 100644 --- a/tests/streams/index_change_test.go +++ b/tests/streams/index_change_test.go @@ -22,14 +22,14 @@ var ( ) func TestIndexChange(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "index_change_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ withTestIndexChangeSetup(testIndexChange(t)), withTestIndexChangeSetup(testYoYIndexChange(t)), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func withTestIndexChangeSetup(test func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/tests/streams/multi_level_composed_test.go b/tests/streams/multi_level_composed_test.go index c184114f0..9f362b545 100644 --- a/tests/streams/multi_level_composed_test.go +++ b/tests/streams/multi_level_composed_test.go @@ -32,13 +32,13 @@ var ( // TestMultiLevelComposedStreams tests multi level composed streams // which tests mainly about composed -> composed streams. func TestMultiLevelComposedStreams(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "multi_level_composed_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ setupMultiLevelComposedStreams(testMultiLevelFunc(t)), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func setupMultiLevelComposedStreams(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/tests/streams/other/other_test.go b/tests/streams/other/other_test.go index 7d012ca99..60df041fc 100644 --- a/tests/streams/other/other_test.go +++ b/tests/streams/other/other_test.go @@ -28,7 +28,7 @@ var defaultStreamLocator = types.StreamLocator{ // [OTHER01] All referenced addresses must be lowercased and valid EVM addresses starting with `0x`. // TestAddressValidation tests that all referenced addresses must be lowercased and valid EVM addresses starting with `0x`. func TestAddressValidation(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "address_validation_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -82,13 +82,13 @@ func TestAddressValidation(t *testing.T) { return nil }, }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } // [OTHER02] Stream ids must respect the following regex: `^st[a-z0-9]{30}$` and be unique by each stream owner. // TestStreamIDValidation tests that stream ids must respect the following regex: `^st[a-z0-9]{30}$` func TestStreamIDValidation(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "stream_id_validation_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -193,20 +193,20 @@ func TestStreamIDValidation(t *testing.T) { return nil }, }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } // [OTHER03] Any user can create a stream. // TestAnyUserCanCreateStream tests that any user with a valid Ethereum address can create a stream func TestAnyUserCanCreateStream(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "any_user_can_create_stream_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testAnyUserCanCreateStream(t), testAnyUserCanCreateStream(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } // testAnyUserCanCreateStream tests that any user with a valid Ethereum address can create a stream @@ -250,13 +250,13 @@ func testAnyUserCanCreateStream(t *testing.T) func(ctx context.Context, platform // [OTHER04] Multiple streams can be created in a single transaction. // TestMultipleStreamCreation tests that multiple streams can be created in a single transaction using CreateStreams func TestMultipleStreamCreation(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "multiple_stream_creation_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testMultipleStreamCreation(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } // testMultipleStreamCreation tests that multiple streams can be created in a single transaction diff --git a/tests/streams/other/stream_exists_batch_test.go b/tests/streams/other/stream_exists_batch_test.go index 515b9af68..a81a0c384 100644 --- a/tests/streams/other/stream_exists_batch_test.go +++ b/tests/streams/other/stream_exists_batch_test.go @@ -22,13 +22,13 @@ import ( // paths are covered. This gives clear coverage of core existence logic // without going through higher-level helpers like filter_streams_by_existence. func TestSTREAM_EXISTS_BATCH01_Direct(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "stream_exists_batch_direct_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testStreamExistsBatchDirect(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testStreamExistsBatchDirect(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/tests/streams/pending_prune_days_test.go b/tests/streams/pending_prune_days_test.go index 42a035a49..8a7afd641 100644 --- a/tests/streams/pending_prune_days_test.go +++ b/tests/streams/pending_prune_days_test.go @@ -21,24 +21,24 @@ import ( // Minimal, high-ROI test that verifies pending_prune_days is populated on insert // and remains idempotent for multiple inserts within the same day. func TestPendingPruneDaysEnqueue(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "pending_prune_days_enqueue_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testPendingPruneDays(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } // Same minimal assertions for the truflation batch path (truflation_insert_records) func TestPendingPruneDaysEnqueue_Truflation(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "pending_prune_days_enqueue_truflation_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testPendingPruneDaysTruflation(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testPendingPruneDays(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/tests/streams/primitive_batch_insert_alignment_test.go b/tests/streams/primitive_batch_insert_alignment_test.go index 0ae4534df..a0a710750 100644 --- a/tests/streams/primitive_batch_insert_alignment_test.go +++ b/tests/streams/primitive_batch_insert_alignment_test.go @@ -30,13 +30,13 @@ import ( // The test creates two streams and inserts interleaved data, then verifies each stream // contains only its own data by checking specific value/stream_ref combinations. func TestPrimitiveBatchInsertAlignment(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "primitive_batch_insert_alignment_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testBatchAlignment(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testBatchAlignment(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/tests/streams/primitive_test.go b/tests/streams/primitive_test.go index 5a25a06ec..6adb7f68b 100644 --- a/tests/streams/primitive_test.go +++ b/tests/streams/primitive_test.go @@ -21,13 +21,13 @@ const primitiveStreamName = "primitive_stream_000000000000001" var primitiveStreamId = util.GenerateStreamId(primitiveStreamName) func TestPrimitiveStream(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "primitive_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testPRIMITIVE01_DataInsertion(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testPRIMITIVE01_DataInsertion(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/tests/streams/query/metadata_test.go b/tests/streams/query/metadata_test.go index 44d869452..af2897e15 100644 --- a/tests/streams/query/metadata_test.go +++ b/tests/streams/query/metadata_test.go @@ -34,7 +34,7 @@ var ( // TestQUERY04Metadata tests the insertion and retrieval of metadata // for both primitive and composed streams. Also tests disabling metadata and attempting to retrieve it. func TestQUERY04Metadata(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "metadata_insertion_and_retrieval", FunctionTests: []kwilTesting.TestFunc{ WithMetadataTestSetup(testMetadataInsertionAndRetrieval(t, primitiveContractInfo)), @@ -46,7 +46,7 @@ func TestQUERY04Metadata(t *testing.T) { WithMetadataTestSetup(testListMetadataByHeightInvalidPagination(t, primitiveContractInfo)), }, SeedScripts: migrations.GetSeedScriptPaths(), - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func WithMetadataTestSetup(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/tests/streams/query/query_test.go b/tests/streams/query/query_test.go index c4aa59a01..01244e69d 100644 --- a/tests/streams/query/query_test.go +++ b/tests/streams/query/query_test.go @@ -50,7 +50,7 @@ var composedStreamId = util.GenerateStreamId(composedStreamName) var primitiveChildStreamId = util.GenerateStreamId(primitiveChildStreamName) func TestQueryStream(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "query_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -76,7 +76,7 @@ func TestQueryStream(t *testing.T) { WithQueryTestSetup(test_GetIndexChangeUntilSpecificTime(t)), WithQueryTestSetup(testBackwardCompatibility_GetRecord(t)), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } // TestConfig holds the configuration for testing streams diff --git a/tests/streams/roles/permission_gates_test.go b/tests/streams/roles/permission_gates_test.go index 978e4d4e7..94a5fdfbd 100644 --- a/tests/streams/roles/permission_gates_test.go +++ b/tests/streams/roles/permission_gates_test.go @@ -16,7 +16,7 @@ import ( ) func TestPermissionGates(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "permission_gates_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -25,7 +25,7 @@ func TestPermissionGates(t *testing.T) { return nil }, }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testStreamCreationPermissionGates(t *testing.T, ctx context.Context, platform *kwilTesting.Platform) { diff --git a/tests/streams/roles/role_management_test.go b/tests/streams/roles/role_management_test.go index bf980f632..a0d33b8b1 100644 --- a/tests/streams/roles/role_management_test.go +++ b/tests/streams/roles/role_management_test.go @@ -81,7 +81,7 @@ func TestRoleManagementSuite(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: tc.name, SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -94,7 +94,7 @@ func TestRoleManagementSuite(t *testing.T) { return nil // Errors are handled by require/assert }, }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) }) } } diff --git a/tests/streams/taxonomy_query_actions_test.go b/tests/streams/taxonomy_query_actions_test.go index 2e312d2e0..8bfccac1b 100644 --- a/tests/streams/taxonomy_query_actions_test.go +++ b/tests/streams/taxonomy_query_actions_test.go @@ -31,7 +31,7 @@ const ( // TestTaxonomyQueryActions tests the new taxonomy query actions func TestTaxonomyQueryActions(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "taxonomy_query_actions_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -47,7 +47,7 @@ func TestTaxonomyQueryActions(t *testing.T) { testListTaxonomiesByHeightInvalidPagination(t), testGetTaxonomiesForStreamsMismatchedArrays(t), }, - }, testutils.GetTestOptionsWithCache().Options) + }, testutils.GetTestOptionsWithCache()) } // testListTaxonomiesByHeight tests basic height-based taxonomy querying diff --git a/tests/streams/truflation_composed_frozen_test.go b/tests/streams/truflation_composed_frozen_test.go index ec387ba73..7872f1a73 100644 --- a/tests/streams/truflation_composed_frozen_test.go +++ b/tests/streams/truflation_composed_frozen_test.go @@ -29,7 +29,7 @@ var ( ) func TestTruflationComposedFrozen(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "truflation_composed_frozen_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -42,7 +42,7 @@ func TestTruflationComposedFrozen(t *testing.T) { setupTruflationFrozenComposedTest(testTruflationIndexWithBaseTime(t)), setupTruflationFrozenComposedTest(testTruflationIndexRangeQuery(t)), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func setupTruflationFrozenComposedTest(testFn func(ctx context.Context, platform *kwilTesting.Platform) error) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/tests/streams/truflation_primitive_frozen_test.go b/tests/streams/truflation_primitive_frozen_test.go index 0cea0a6e7..e63bb1c26 100644 --- a/tests/streams/truflation_primitive_frozen_test.go +++ b/tests/streams/truflation_primitive_frozen_test.go @@ -24,7 +24,7 @@ var ( ) func TestTruflationFrozen(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "truflation_frozen_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ @@ -39,7 +39,7 @@ func TestTruflationFrozen(t *testing.T) { setupTruflationFrozenTest(testWithoutFrozen(t)), setupTruflationFrozenTest(testFutureFrozen(t)), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } // Test Case 1 & 9: All records before frozen diff --git a/tests/streams/utils/runner.go b/tests/streams/utils/runner.go index b14b0982d..858e25cbe 100644 --- a/tests/streams/utils/runner.go +++ b/tests/streams/utils/runner.go @@ -1,3 +1,5 @@ +//go:build kwiltest + // Package testutils provides the main test orchestration logic for cache and ERC-20 bridge testing package testutils @@ -29,6 +31,8 @@ type Options struct { // RunSchemaTest is a wrapper around kwilTesting.RunSchemaTest that automatically // handles both cache and ERC-20 bridge setup. +// it's necessary to use this instead of kwilTesting.RunSchemaTest because we need to clean up the ordered-sync extension after each test. +// otherwise, we will get the "already initialized" error func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { // Convert to kwilTesting.Options - use type assertion since TestingT is an alias for testing.T testT := t.(*testing.T) @@ -71,15 +75,28 @@ func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { wrappedTests = wrapWithExtensionsSetup(context.Background(), s.FunctionTests, cacheConfig, erc20Config, kwilOpts) } - // Ensure isolation: reset ordered-sync in-memory topics before test run - orderedsync.ForTestingReset() + // Run each function test in isolation so that ordered-sync is reset + // before the interpreter is created for that function. Resetting inside + // the function is too late because EngineReadyHooks run at interpreter init. + if len(wrappedTests) <= 1 { + // Single function: simple path + orderedsync.ForTestingReset() + kwilTesting.RunSchemaTest(testT, kwilTesting.SchemaTest{ + Name: s.Name, + SeedScripts: s.SeedScripts, + FunctionTests: wrappedTests, + }, kwilOpts) + return + } - // Run with wrapper - kwilTesting.RunSchemaTest(testT, kwilTesting.SchemaTest{ - Name: s.Name, - SeedScripts: s.SeedScripts, - FunctionTests: wrappedTests, - }, kwilOpts) + for _, fn := range wrappedTests { + orderedsync.ForTestingReset() + kwilTesting.RunSchemaTest(testT, kwilTesting.SchemaTest{ + Name: s.Name, + SeedScripts: s.SeedScripts, + FunctionTests: []kwilTesting.TestFunc{fn}, + }, kwilOpts) + } } // wrapWithExtensionsSetup wraps test functions with cache extension initialization @@ -109,8 +126,6 @@ func wrapWithExtensionsSetup(ctx context.Context, originalFuncs []kwilTesting.Te cleanups = append(cleanups, cleanup) } - // ERC-20 bridge setup removed - handle directly in tests with WithERC20TestSetup - // Run original test err := originalFn(ctx, platform) if err != nil { From 42babb539e12f091805552f7d45d56125006b9ff Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Fri, 12 Sep 2025 11:23:20 -0300 Subject: [PATCH 16/18] refactor(tests): update test files to use testutils.RunSchemaTest for improved caching This commit modifies several test files to replace instances of `kwilTesting.RunSchemaTest` with `testutils.RunSchemaTest`, ensuring consistent use of the new caching mechanism. The changes enhance test reliability and maintainability by improving cache management during test execution. Key updates include adjustments in `engine_ops_integration_test.go`, `backward_compatibility_test.go`, and `digest_benchmark_test.go`, aligning with the ongoing efforts to streamline the testing framework. --- extensions/tn_cache/internal/engine_ops_integration_test.go | 4 ++-- extensions/tn_digest/engine_ops_integration_test.go | 1 + internal/benchmark/digest/digest_benchmark_test.go | 1 + tests/streams/backward_compatibility_test.go | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/extensions/tn_cache/internal/engine_ops_integration_test.go b/extensions/tn_cache/internal/engine_ops_integration_test.go index 382ab5904..e31a80b40 100644 --- a/extensions/tn_cache/internal/engine_ops_integration_test.go +++ b/extensions/tn_cache/internal/engine_ops_integration_test.go @@ -329,13 +329,13 @@ func testEngineOperationsIntegration(t *testing.T) func(ctx context.Context, pla // TestEngineOperations_RealTimeData tests engine operations with streams that have real-time data updates func TestEngineOperations_RealTimeData(t *testing.T) { - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ Name: "tn_ops_realtime_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwilTesting.TestFunc{ testEngineOperationsRealTime(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testEngineOperationsRealTime(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { diff --git a/extensions/tn_digest/engine_ops_integration_test.go b/extensions/tn_digest/engine_ops_integration_test.go index 03f0dae14..2be736f8f 100644 --- a/extensions/tn_digest/engine_ops_integration_test.go +++ b/extensions/tn_digest/engine_ops_integration_test.go @@ -15,6 +15,7 @@ import ( kwilTesting "github.com/trufnetwork/kwil-db/testing" "github.com/trufnetwork/node/extensions/tn_digest/internal" digestembed "github.com/trufnetwork/node/tests/extensions/digest" + testutils "github.com/trufnetwork/node/tests/streams/utils" ) // TestBuildAndBroadcastAutoDigestTx_VerifiesTxBuildSignAndDBEffect diff --git a/internal/benchmark/digest/digest_benchmark_test.go b/internal/benchmark/digest/digest_benchmark_test.go index e6336ed12..683b5e507 100644 --- a/internal/benchmark/digest/digest_benchmark_test.go +++ b/internal/benchmark/digest/digest_benchmark_test.go @@ -8,6 +8,7 @@ import ( "testing" kwilTesting "github.com/trufnetwork/kwil-db/testing" + testutils "github.com/trufnetwork/node/tests/streams/utils" utils "github.com/trufnetwork/node/tests/streams/utils" "github.com/trufnetwork/node/tests/streams/utils/procedure" "github.com/trufnetwork/sdk-go/core/util" diff --git a/tests/streams/backward_compatibility_test.go b/tests/streams/backward_compatibility_test.go index 464578ac5..102259563 100644 --- a/tests/streams/backward_compatibility_test.go +++ b/tests/streams/backward_compatibility_test.go @@ -13,13 +13,13 @@ import ( ) func TestBackwardCompatibility(t *testing.T) { - kwiltesting.RunSchemaTest(t, kwiltesting.SchemaTest{ + testutils.RunSchemaTest(t, kwiltesting.SchemaTest{ Name: "backward_compatibility_test", SeedScripts: migrations.GetSeedScriptPaths(), FunctionTests: []kwiltesting.TestFunc{ testBackwardCompatibilityFunc(t), }, - }, testutils.GetTestOptions()) + }, testutils.GetTestOptionsWithCache()) } func testBackwardCompatibilityFunc(t *testing.T) kwiltesting.TestFunc { From 3a2c9be119f0d058f26e3da11ca76cd067846f5e Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Fri, 12 Sep 2025 11:36:56 -0300 Subject: [PATCH 17/18] refactor(tests): enhance RunSchemaTest to include SeedStatements, TestCases, and Owner This commit updates the `RunSchemaTest` function in the `runner.go` file to include additional fields: `SeedStatements`, `TestCases`, and `Owner` in the `kwilTesting.SchemaTest` struct. These enhancements aim to improve the flexibility and detail of schema tests, allowing for more comprehensive test configurations and better test management. This change aligns with ongoing efforts to refine the testing framework and improve overall test reliability. --- tests/streams/utils/runner.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/streams/utils/runner.go b/tests/streams/utils/runner.go index 858e25cbe..348ed30b2 100644 --- a/tests/streams/utils/runner.go +++ b/tests/streams/utils/runner.go @@ -82,9 +82,12 @@ func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { // Single function: simple path orderedsync.ForTestingReset() kwilTesting.RunSchemaTest(testT, kwilTesting.SchemaTest{ - Name: s.Name, - SeedScripts: s.SeedScripts, - FunctionTests: wrappedTests, + Name: s.Name, + SeedScripts: s.SeedScripts, + SeedStatements: s.SeedStatements, + FunctionTests: wrappedTests, + TestCases: s.TestCases, + Owner: s.Owner, }, kwilOpts) return } @@ -92,9 +95,12 @@ func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { for _, fn := range wrappedTests { orderedsync.ForTestingReset() kwilTesting.RunSchemaTest(testT, kwilTesting.SchemaTest{ - Name: s.Name, - SeedScripts: s.SeedScripts, - FunctionTests: []kwilTesting.TestFunc{fn}, + Name: s.Name, + SeedScripts: s.SeedScripts, + SeedStatements: s.SeedStatements, + FunctionTests: []kwilTesting.TestFunc{fn}, + TestCases: s.TestCases, + Owner: s.Owner, }, kwilOpts) } } From 4e26a93b06d17904bef3d15b228aed28e64fc1cc Mon Sep 17 00:00:00 2001 From: Raffael Campos Date: Fri, 12 Sep 2025 11:40:13 -0300 Subject: [PATCH 18/18] fix(tests): make test case handling a fatal error in RunSchemaTest This commit updates the `RunSchemaTest` function in the `runner.go` file to enforce a fatal error when test cases are provided, as they are not currently supported. This change clarifies the limitations of the function and sets the groundwork for potential future support of test cases. The adjustment aims to improve error handling and provide clearer feedback during test execution. --- tests/streams/utils/runner.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/streams/utils/runner.go b/tests/streams/utils/runner.go index 348ed30b2..1efbf5864 100644 --- a/tests/streams/utils/runner.go +++ b/tests/streams/utils/runner.go @@ -75,6 +75,13 @@ func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { wrappedTests = wrapWithExtensionsSetup(context.Background(), s.FunctionTests, cacheConfig, erc20Config, kwilOpts) } + // here we still don't support test cases + // let's make it fatal error + // but if needed it's easy to support it in the future + if len(s.TestCases) > 0 { + testT.Fatalf("test cases are not supported yet") + } + // Run each function test in isolation so that ordered-sync is reset // before the interpreter is created for that function. Resetting inside // the function is too late because EngineReadyHooks run at interpreter init. @@ -86,7 +93,6 @@ func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { SeedScripts: s.SeedScripts, SeedStatements: s.SeedStatements, FunctionTests: wrappedTests, - TestCases: s.TestCases, Owner: s.Owner, }, kwilOpts) return @@ -99,7 +105,6 @@ func RunSchemaTest(t TestingT, s kwilTesting.SchemaTest, options *Options) { SeedScripts: s.SeedScripts, SeedStatements: s.SeedStatements, FunctionTests: []kwilTesting.TestFunc{fn}, - TestCases: s.TestCases, Owner: s.Owner, }, kwilOpts) }