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 diff --git a/extensions/tn_cache/internal/engine_ops_integration_test.go b/extensions/tn_cache/internal/engine_ops_integration_test.go index 238c5524c..e31a80b40 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 @@ -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 { @@ -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..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 @@ -28,7 +29,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/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/internal/benchmark/digest/digest_benchmark_test.go b/internal/benchmark/digest/digest_benchmark_test.go index 166f38677..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" @@ -195,13 +196,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/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..b9717ebbb --- /dev/null +++ b/internal/migrations/test_only/bootstrap_erc20.sql @@ -0,0 +1,27 @@ +-- 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 +-- 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/README.md b/tests/extensions/erc20/README.md new file mode 100644 index 000000000..d65a7db37 --- /dev/null +++ b/tests/extensions/erc20/README.md @@ -0,0 +1,63 @@ +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" +} +``` + +### 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 new file mode 100644 index 000000000..ba34f1333 --- /dev/null +++ b/tests/extensions/erc20/common_test.go @@ -0,0 +1,86 @@ +//go:build kwiltest + +package tests + +import ( + "context" + "fmt" + "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" + + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" +) + +// Common deterministic values used across ERC20 bridge tests +const ( + 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 +func seedAndRun(t TestingT, name string, fn kwilTesting.TestFunc) { + seedScripts := migrations.GetSeedScriptPaths() + + // Wrap the test function to add singleton reset and cleanup + wrappedFn := func(ctx context.Context, platform *kwilTesting.Platform) error { + // STEP 1: Register cleanup (runs after transaction rollback) + t.Cleanup(func() { + erc20shim.ForTestingClearAllInstances(ctx, platform) + erc20shim.ForTestingResetSingleton() + }) + + // 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) + } + 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()) +} 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..a24120497 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_actions_smoke_test.go @@ -0,0 +1,115 @@ +//go:build kwiltest + +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", func(ctx context.Context, platform *kwilTesting.Platform) error { + + // Singleton reset and initialize is handled by seedAndRun + // Ensure active+synced in-memory + err := erc20shim.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, TestEscrowA, TestERC20, 18, 60, TestExtensionAlias) + 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, TestExtensionAlias, "info", []any{}, func(row *common.Row) error { + 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, TestExtensionAlias, "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, TestExtensionAlias, "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, TestExtensionAlias, "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..70d0cf67b --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_admin_authz_test.go @@ -0,0 +1,79 @@ +//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" + 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", 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, TestExtensionAlias) + require.NoError(t, err) + + // Step 1: Inject deposit so user has balance + 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, TestExtensionAlias, 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, 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, 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, TestExtensionAlias, "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, TestExtensionAlias, 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_consecutive_test.go b/tests/extensions/erc20/erc20_bridge_consecutive_test.go new file mode 100644 index 000000000..df03edec3 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_consecutive_test.go @@ -0,0 +1,86 @@ +//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", 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, 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, TestExtensionAlias, "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", 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, TestExtensionAlias) + if err != nil { + // If this fails, let's check the DB state to debug + var isActive bool + 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 { + 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, TestExtensionAlias, "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 new file mode 100644 index 000000000..eb91f4569 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_end_to_end_test.go @@ -0,0 +1,128 @@ +//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" + 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" +) + +// 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", 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, 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, TestExtensionAlias, "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, platform, TestChain, TestEscrowA, TestERC20, TestUserA, TestEscrowA, TestAmount1, 10, nil) + require.NoError(t, err) + + // Verify user has the balance + balance, err := testerc20.GetUserBalance(ctx, platform, TestExtensionAlias, 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) + r, err := platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "bridge", []any{amtDec}, func(row *common.Row) error { + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + return r.Error + } + + // 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 3-4: Deterministically finalize current epoch and confirm + var bh [32]byte + 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, TestExtensionAlias, "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, TestExtensionAlias, "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 + } + 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 new file mode 100644 index 000000000..94f17f85b --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_epoch_test.go @@ -0,0 +1,77 @@ +//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" + 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" +) + +// TestERC20BridgeEpochFlow validates lock-and-issue, finalize and confirm using shims. +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 a different escrow for this test to avoid conflicts with seeded instance + chain := "sepolia" + escrow := "0xdddddddddddddddddddddddddddddddddddddddd" + erc20 := "0x2222222222222222222222222222222222222222" + user := "0xabc0000000000000000000000000000000000001" + 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, TestExtensionAlias)) + + // Credit balance via injected transfer (simulates inbound deposit) + 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, 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 { + 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 and confirm via helper + var bh [32]byte + require.NoError(t, erc20shim.ForTestingFinalizeAndConfirmCurrentEpoch(ctx, platform, chain, escrow, 11, bh)) + + // 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: Disable the test instance (tears down runtimes + resets singleton) + 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 new file mode 100644 index 000000000..f22d4962c --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_injection_test.go @@ -0,0 +1,68 @@ +//go:build kwiltest + +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" + + 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) { + 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" + erc20 := "0x2222222222222222222222222222222222222222" + user := "0xabc0000000000000000000000000000000000001" + value := "1000000000000000000" + + // Enable instance with alias for injection test + 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, TestExtensionAlias) + }) + + // 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) + + // 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, 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)) + } + 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 + }) +} 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..a4783116a --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_multi_instance_test.go @@ -0,0 +1,95 @@ +//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" + 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 { + // 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.ForTestingSeedAndActivateInstance(ctx, platform, TestChain, escrowA, TestERC20, 18, 60, aliasA) + require.NoError(t, err) + 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, platform, 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 + } + if val, ok := row.Values[0].(*types.Decimal); ok { + balanceA = val + } + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + 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) + 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 + } + if val, ok := row.Values[0].(*types.Decimal); ok { + balanceB = val + } + return nil + }) + require.NoError(t, err) + if r != nil && r.Error != nil { + 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 + 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_simple_test.go b/tests/extensions/erc20/erc20_bridge_simple_test.go new file mode 100644 index 000000000..8869105e4 --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_simple_test.go @@ -0,0 +1,96 @@ +//go:build kwiltest + +package tests + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + erc20shim "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" +) + +// 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) { + 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, TestExtensionAlias) + require.NoError(t, err) + + // Run the actual test logic + testSimpleBalanceTx(t, platform) + return nil + }) +} + +// 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" + + // Query balance for a wallet with no prior deposits + balance, err := testerc20.GetUserBalance(context.Background(), txPlatform, TestExtensionAlias, 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") +} + +// 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. 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) { + 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, TestExtensionAlias) + require.NoError(t, err) + + // Run the actual test logic + testAdminLockTx(t, platform) + return nil + }) +} + +// 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, TestChain, TestEscrowA, 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, 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, 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 new file mode 100644 index 000000000..d771c88ae --- /dev/null +++ b/tests/extensions/erc20/erc20_bridge_transfer_test.go @@ -0,0 +1,75 @@ +//go:build kwiltest + +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", 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, TestExtensionAlias) + require.NoError(t, err) + + // Step 1: Inject deposit for userA + 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, 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, TestExtensionAlias, 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) + + r, err := platform.Engine.Call(engineCtx, platform.DB, TestExtensionAlias, "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 + 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, TestExtensionAlias, TestUserB) + require.NoError(t, err) + require.Equal(t, TestAmount1, balanceB, "userB should have received transferred amount") + + return nil + }) +} 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/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/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/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 { 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/complex_composed_test.go b/tests/streams/complex_composed_test.go index ceb73af26..a844a17ab 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,7 +52,7 @@ 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) { + 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) @@ -57,7 +60,7 @@ func wrapTestWithCacheModes(t *testing.T, testName string, testFunc func(*testin }) }) t.Run(testName+"_with_cache", func(t *testing.T) { - helper.RunInTx(t, func(t *testing.T, txPlatform *kwilTesting.Platform) { + 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) @@ -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/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/cache/helper.go b/tests/streams/utils/cache/helper.go new file mode 100644 index 000000000..fdf7f3a2b --- /dev/null +++ b/tests/streams/utils/cache/helper.go @@ -0,0 +1,175 @@ +// 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() { + // Clear all in-memory overrides to avoid cross-test leakage + tn_cache.SetTestConfiguration(nil) + tn_cache.SetTestDBConfiguration(config.DBConfig{}) + 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..9ac5ee0db --- /dev/null +++ b/tests/streams/utils/erc20/config.go @@ -0,0 +1,185 @@ +// Package erc20 provides configuration and setup for ERC-20 bridge testing +package erc20 + +import ( + "fmt" + "net/url" + "os" + "strconv" + "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. +// +// 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, + 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 == os.DevNull { + 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) + 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) + } + } + + 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..0e8f394da --- /dev/null +++ b/tests/streams/utils/erc20/helper.go @@ -0,0 +1,108 @@ +//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 + +import ( + "context" + "fmt" + "math/big" + "testing" + + "github.com/trufnetwork/kwil-db/common" + "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" +) + +// WithERC20TestSetupTx is a transaction-aware version that works with WithTx for perfect isolation +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, chain, escrowAddr, alias) + erc20shim.ForTestingResetSingleton() + }) + + if err := erc20shim.ForTestingSeedAndActivateInstance( + context.Background(), txPlatform, chain, escrowAddr, + "0x2222222222222222222222222222222222222222", 18, 60, alias, + ); err != nil { + t.Fatalf("seed/activate ERC20 instance failed: %v", err) + } + } +} + +// 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, extensionAlias, escrowAddr, userAddr, amount string) error { + // Use the platform's DB and Engine (could be transaction-scoped) + return InjectERC20Transfer( + 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, extensionAlias, 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} + + var balance string + 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)) + } + 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 +} + +// CallLockAdmin executes the lock_admin system method with OverrideAuthz. +// This simulates an admin locking user tokens. +func CallLockAdmin(ctx context.Context, platform *kwilTesting.Platform, extensionAlias, 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} + // 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(bn, 0) + if err != nil { + return fmt.Errorf("failed to create decimal: %w", err) + } + amtDec.SetPrecisionAndScale(78, 0) + + 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 +} diff --git a/tests/streams/utils/erc20/inject.go b/tests/streams/utils/erc20/inject.go new file mode 100644 index 000000000..94d6d5938 --- /dev/null +++ b/tests/streams/utils/erc20/inject.go @@ -0,0 +1,97 @@ +//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" + 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, 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, platform, 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, 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, platform, &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 +} 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/eventstore/mock.go b/tests/streams/utils/eventstore/mock.go new file mode 100644 index 000000000..9a962f440 --- /dev/null +++ b/tests/streams/utils/eventstore/mock.go @@ -0,0 +1,120 @@ +// Package eventstore provides mock implementations of event stores for testing +package eventstore + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/trufnetwork/kwil-db/extensions/listeners" +) + +// mockEventStore implements listeners.EventStore for testing +type mockEventStore struct { + mu sync.RWMutex + events chan broadcastEvent + 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 broadcastEvent, 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 { + ev := broadcastEvent{ + eventType: eventType, + 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 <- ev: + default: + // Channel full, skip + } + return nil +} + +// Set implements listeners.EventKV +func (m *mockEventStore) Set(ctx context.Context, key []byte, value []byte) error { + 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 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 +} + +// 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() + + // 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) + case ev := <-m.events: + if ev.eventType == eventType { + return nil + } + } + } +} + +// GetBroadcastEvents returns all broadcast events +func (m *mockEventStore) GetBroadcastEvents() []broadcastEvent { + 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 +type MockEventStore = mockEventStore diff --git a/tests/streams/utils/runner.go b/tests/streams/utils/runner.go new file mode 100644 index 000000000..1efbf5864 --- /dev/null +++ b/tests/streams/utils/runner.go @@ -0,0 +1,204 @@ +//go:build kwiltest + +// 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" + + orderedsync "github.com/trufnetwork/kwil-db/node/exts/ordered-sync" +) + +// 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. +// 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) + 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) + } + + // 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. + if len(wrappedTests) <= 1 { + // Single function: simple path + orderedsync.ForTestingReset() + kwilTesting.RunSchemaTest(testT, kwilTesting.SchemaTest{ + Name: s.Name, + SeedScripts: s.SeedScripts, + SeedStatements: s.SeedStatements, + FunctionTests: wrappedTests, + Owner: s.Owner, + }, kwilOpts) + return + } + + for _, fn := range wrappedTests { + orderedsync.ForTestingReset() + kwilTesting.RunSchemaTest(testT, kwilTesting.SchemaTest{ + Name: s.Name, + SeedScripts: s.SeedScripts, + SeedStatements: s.SeedStatements, + FunctionTests: []kwilTesting.TestFunc{fn}, + Owner: s.Owner, + }, kwilOpts) + } +} + +// 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 { + 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() + 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() { + cleanup, err := setupCacheExtension(ctx, cacheConfig, platform) + if err != nil { + testErr = fmt.Errorf("failed to setup cache extension: %w", err) + return + } + cleanups = append(cleanups, cleanup) + } + + // Run original test + err := originalFn(ctx, platform) + if err != nil { + testErr = err + } + + 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 { + 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) + } + + // Register extension for cleanup + tn_cache.SetExtension(ext) + + cleanup := func() { + // 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) + // Cleanup helper last + helper.Cleanup() + } + + return cleanup, nil +} + +// setupERC20Bridge sets up the ERC-20 bridge for testing +// ERC20 setup removed - now handled directly in tests using WithERC20TestSetup + +// TestingT interface for test functions +type TestingT interface { + Fatalf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) +} + +// WithExtensions removed - ERC20 setup now handled directly in tests 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..32b61a803 100644 --- a/tests/streams/utils/utils.go +++ b/tests/streams/utils/utils.go @@ -1,22 +1,20 @@ +// 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/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" ) // init registers tn_cache precompiles globally for tests @@ -27,199 +25,40 @@ 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 -} +// ============================================================================ +// BACKWARD COMPATIBILITY RE-EXPORTS +// ============================================================================ -// 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 -} +// Type Aliases for backward compatibility +type CacheOptions = cache.CacheOptions +type StreamConfig = cache.StreamConfig -// 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 -} +// ERC20 types removed - using simple pattern instead of complex fixtures +type CacheTestHelper = cache.CacheTestHelper -// 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 -} +// ============================================================================ +// CACHE FUNCTIONS (RE-EXPORTS) +// ============================================================================ -// 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 configuration builders +func NewCacheOptions() *CacheOptions { return cache.NewCacheOptions() } -// Build converts the options into the metadata map for the extension -func (c *CacheOptions) Build() map[string]string { - metadata := make(map[string]string) +// ============================================================================ +// ERC-20 FUNCTIONS (RE-EXPORTS) +// ============================================================================ - // Always set enabled status - metadata["enabled"] = strconv.FormatBool(c.enabled) +// ERC-20 configuration builders removed - using simple pattern - // 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 -} - -// Options extends kwilTesting.Options with cache configuration -type Options struct { - *kwilTesting.Options - Cache *CacheOptions - DisableCache bool // Explicitly disable cache (overrides default) -} +// ============================================================================ +// CONVENIENCE FUNCTIONS +// ============================================================================ -// 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 +66,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 +74,20 @@ 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 { - return &Options{ - Options: &kwilTesting.Options{ - UseTestContainer: true, - }, - // Cache is enabled by default with no specific configuration - } -} - -// 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{ - 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 - } - } - - // 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 - } - - // Run with wrapper - kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{ - Name: s.Name, - SeedScripts: s.SeedScripts, - FunctionTests: wrapWithCacheSetup(context.Background(), s.FunctionTests, cacheConfig, kwilOpts), - }, kwilOpts) +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) } -// 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 - } +// ERC-20 functions removed - using simple pattern instead of complex configurations - 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 +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ - // Run original test - err = originalFn(ctx, platform) - if err != nil { - testErr = err - return - } - - // 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) - } - } - - if ext.SyncChecker() != nil { - ext.SyncChecker().Stop() - } - - defer ext.Close() - - return - } - } - return wrapped +func Ptr[T any](v T) *T { + return &v }