mukan-ignite/ignite/pkg/cosmostxcollector/adapter/postgres/postgres_test.go
Mukan Erkin Törük 26b204bd04
Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
feat: fork Ignite CLI v29 as Mukan Ignite — remove cosmos-sdk restrictions
2026-05-11 03:31:37 +03:00

550 lines
12 KiB
Go

package postgres
import (
"context"
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"testing"
"testing/fstest"
"time"
"github.com/DATA-DOG/go-sqlmock"
abci "github.com/cometbft/cometbft/abci/types"
ctypes "github.com/cometbft/cometbft/rpc/core/types"
"github.com/lib/pq"
"github.com/stretchr/testify/require"
"github.com/ignite/cli/v29/ignite/pkg/cosmosclient"
"github.com/ignite/cli/v29/ignite/pkg/cosmostxcollector/query"
"github.com/ignite/cli/v29/ignite/pkg/errors"
)
var (
eventFields = []string{"id", "index", "tx_hash", "type", "created_at"}
eventAttrFields = []string{"event_id", "name", "value"}
)
func TestUpdateSchema(t *testing.T) {
// Arrange
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
ctx := context.Background()
tplSchemaScript := `BEGIN;
INSERT INTO schema(version)
VALUES(%d)
;%sCOMMIT;`
// Arrange: Schema files
schemasData := []string{"/* FOO */", "/* BAR */"}
fs := fstest.MapFS{
"schemas/1.sql": &fstest.MapFile{Data: []byte(schemasData[0])},
"schemas/2.sql": &fstest.MapFile{Data: []byte(schemasData[1])},
}
s := NewSchemas(fs, "")
// Arrange: Prepare database adapter
adapter := Adapter{
db: db,
schemas: s,
}
// Arrange: Database mock and expectations
mock.
ExpectExec(s.GetTableDDL()).
WillReturnResult(
// DDL execution won't affect any rows or IDs
sqlmock.NewResult(0, 0),
)
mock.
ExpectQuery(s.GetSchemaVersionSQL()).
WillReturnRows(
// Zero is returned to signal that there are no versions applied.
// When no versions are applied schema walk will start from version 1.
sqlmock.NewRows([]string{"version"}).AddRow(uint64(0)),
)
for i, data := range schemasData {
version := i + 1
script := fmt.Sprintf(tplSchemaScript, version, data)
// Add database mock and expectation for the current schema version
mock.
ExpectExec(script).
WillReturnResult(sqlmock.NewResult(0, 0))
}
// Act
err := adapter.UpdateSchema(ctx, s)
// Assert
require.NoError(t, err)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestSave(t *testing.T) {
// Arrange
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
adapter := Adapter{db: db}
ctx := context.Background()
hash := "F2564C78071E26643AE9B3E2A19FA0DC10D4D9E873AA0BE808660123F11A1E78"
// Arrange: A Cosmos client TX to save
evtAttr := abci.EventAttribute{
Key: "recipient",
Value: "cosmos1crje20aj4gxdtyct7z3knxqry2jqt2fuaey6u5",
}
evt := abci.Event{
Type: "transfer",
Attributes: []abci.EventAttribute{evtAttr},
}
h, _ := hex.DecodeString(hash) // TODO: How to properly generate TX hash for the result?
tx := cosmosclient.TX{
// Tendermint API search result
Raw: &ctypes.ResultTx{
Hash: h,
Height: 1,
Index: 0,
TxResult: abci.ExecTxResult{
Events: []abci.Event{evt},
},
},
}
// Arrange: JSON of the raw transaction result
jsonResTX, err := json.Marshal(tx.Raw)
require.NoError(t, err)
// Arrange: Database mock and expectations for prepared SQL statements
mock.ExpectBegin()
txStmt := mock.ExpectPrepare(`
INSERT INTO tx (hash, index, height, block_time)
VALUES ($1, $2, $3, $4)
`)
evtStmt := mock.ExpectPrepare(`
INSERT INTO event (tx_hash, type, index)
VALUES ($1, $2, $3) RETURNING id
`)
attrStmt := mock.ExpectPrepare(`
INSERT INTO attribute (event_id, name, value)
VALUES ($1, $2, $3)
`)
// Arrange: Database mock and expectations for INSERT statement executions
insertResult := sqlmock.NewResult(0, 1)
evtIndex := 0
evtID := int64(1)
jsonEvtAttrValue := []byte(fmt.Sprintf(`"%s"`, evtAttr.Value))
mock.
ExpectExec(`
INSERT INTO raw_tx (hash, data)
VALUES ($1, $2)
`).
WithArgs(tx.Raw.Hash.String(), jsonResTX).
WillReturnResult(insertResult)
txStmt.
ExpectExec().
WithArgs(hash, tx.Raw.Index, tx.Raw.Height, tx.BlockTime).
WillReturnResult(insertResult)
evtStmt.
ExpectQuery().
WithArgs(hash, evt.Type, evtIndex).
WillReturnRows(
sqlmock.NewRows([]string{"event_id"}).AddRow(evtID),
)
attrStmt.
ExpectExec().
WithArgs(evtID, evtAttr.Key, jsonEvtAttrValue).
WillReturnResult(insertResult)
mock.ExpectCommit()
// Act
err = adapter.Save(ctx, []cosmosclient.TX{tx})
// Assert
require.NoError(t, err)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestGetLatestHeight(t *testing.T) {
// Arrange
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
adapter := Adapter{db: db}
ctx := context.Background()
// Arrange: Database mock and expectations
wantHeight := int64(42)
mock.
ExpectQuery(sqlSelectBlockHeight).
WillReturnRows(
sqlmock.NewRows([]string{"height"}).AddRow(wantHeight),
)
// Act
height, err := adapter.GetLatestHeight(ctx)
// Assert
require.NoError(t, err)
require.Equal(t, wantHeight, height)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQuery(t *testing.T) {
// Arrange
var rowValue string
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
adapter := Adapter{db: db}
ctx := context.Background()
// Arrange: Query
qry := query.New("baz", query.Fields("foo"))
// Arrange: Database mock and expectations
wantRowValue := "expected"
fields := []string{"foo"}
rows := sqlmock.NewRows(fields).AddRow(wantRowValue)
mock.
ExpectQuery(`
SELECT DISTINCT foo
FROM baz
WHERE true
LIMIT 30 OFFSET 0
`).
WillReturnRows(rows)
// Act
cr, err := adapter.Query(ctx, qry)
if cr.Next() {
err = cr.Scan(&rowValue)
require.NoError(t, err)
}
// Assert
require.NoError(t, err, "expected no query errors on execution")
require.Equal(t, wantRowValue, rowValue)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueryCursor(t *testing.T) {
// Arrange
var (
rowValue string
cursorNextSucceeded bool
err error
)
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
adapter := Adapter{db: db}
ctx := context.Background()
// Arrange: Query
qry := query.New("baz", query.Fields("foo"))
// Arrange: Database mock and expectations
wantRowValue := "expected"
fields := []string{"foo"}
rows := sqlmock.NewRows(fields).AddRow(wantRowValue)
mock.
ExpectQuery(`
SELECT DISTINCT foo
FROM baz
WHERE true
LIMIT 30 OFFSET 0
`).
WillReturnRows(rows)
// Act
cr, _ := adapter.Query(ctx, qry)
if cursorNextSucceeded = cr.Next(); cursorNextSucceeded {
err = cr.Scan(&rowValue)
}
// Assert
require.True(t, cursorNextSucceeded, "expected cursor.Next() to succeed")
require.NoError(t, err, "expected no scan errors on execution")
require.Equal(t, wantRowValue, rowValue)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueryWithFilter(t *testing.T) {
// Arrange
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
adapter := Adapter{db: db}
ctx := context.Background()
// Arrange: Query
wantArg := "bar"
qry := query.New(
"baz",
query.Fields("foo"),
query.WithFilters(
NewFilter("foo", wantArg),
),
)
// Arrange: Database mock and expectations
fields := []string{"baz"}
rows := sqlmock.NewRows(fields)
mock.
ExpectQuery(`
SELECT DISTINCT foo
FROM baz
WHERE foo = $1
LIMIT 30 OFFSET 0
`).
WithArgs(wantArg).
WillReturnRows(rows)
// Act
_, err := adapter.Query(ctx, qry)
// Assert
require.NoError(t, err, "expected no query errors on execution")
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueryError(t *testing.T) {
// Arrange
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
adapter := Adapter{db: db}
ctx := context.Background()
// Arrange: Query
qry := query.New("baz", query.Fields("foo"), query.WithoutPaging())
// Arrange: Database mock and expectations
wantErr := errors.New("expected error")
mock.
ExpectQuery("SELECT DISTINCT foo FROM baz WHERE true").
WillReturnError(wantErr)
// Act
_, err := adapter.Query(ctx, qry)
// Assert
require.Equal(t, wantErr, err)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueryRowError(t *testing.T) {
// Arrange
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
adapter := Adapter{db: db}
ctx := context.Background()
cols := []string{"name"}
// Arrange: Query
qry := query.New("baz", query.Fields(cols[0]), query.WithoutPaging())
// Arrange: Database mock and expectations
wantErr := errors.New("expected error")
row := sqlmock.
NewRows(cols).
AddRow("foo").
RowError(0, wantErr)
mock.
ExpectQuery("SELECT DISTINCT name FROM baz WHERE true").
WillReturnRows(row)
// Act
cr, err := adapter.Query(ctx, qry)
// Assert
require.NoError(t, err, "expected no query errors on execution")
require.False(t, cr.Next(), "expected cursor.Next() to fail")
require.Equal(t, wantErr, cr.Err())
require.NoError(t, mock.ExpectationsWereMet())
}
func TestEventQuery(t *testing.T) {
// Arrange
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
adapter := Adapter{db: db}
ctx := context.Background()
// Arrange: Database mocks
attrName := "foo"
attrValue := []byte("42")
event := query.Event{
ID: 1,
TXHash: "ABC123",
Index: 0,
Type: "test",
Attributes: []query.Attribute{
query.NewAttribute(attrName, attrValue),
},
CreatedAt: time.Now(),
}
eventRows := sqlmock.
NewRows(eventFields).
AddRow(event.ID, event.Index, event.TXHash, event.Type, event.CreatedAt)
eventAttrRows := sqlmock.
NewRows(eventAttrFields).
AddRow(event.ID, attrName, attrValue)
mock.
ExpectQuery(`
SELECT event.id, event.index, event.tx_hash, event.type, event.created_at
FROM event INNER JOIN tx ON event.tx_hash = tx.hash
WHERE true
ORDER BY tx.height, tx.index, event.index
LIMIT 30 OFFSET 0
`).
WillReturnRows(eventRows)
mock.
ExpectQuery(`
SELECT event_id, name, value FROM attribute
WHERE event_id = ANY($1)
ORDER BY event_id
`).
WillReturnRows(eventAttrRows).
WithArgs(pq.Array([]int64{event.ID}))
// Arrange: Expectations
wantEvents := []query.Event{event}
// Arrange: Query
qry := query.NewEventQuery()
// Act
events, err := adapter.QueryEvents(ctx, qry)
// Assert
require.NoError(t, err, "expected no query errors on execution")
require.Len(t, events, 1)
require.Equal(t, wantEvents, events)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestEventQueryWithFilters(t *testing.T) {
// Arrange
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
adapter := Adapter{db: db}
ctx := context.Background()
// Arrange: Database mocks
indexValue := 2
typeValue := "chain.test.Test"
hashValues := []string{"HASH1", "HASH2"}
eventRows := sqlmock.NewRows(eventFields)
mock.
ExpectQuery(`
SELECT event.id, event.index, event.tx_hash, event.type, event.created_at
FROM event INNER JOIN tx ON event.tx_hash = tx.hash
WHERE event.index = $1 AND event.type = $2 AND event.tx_hash = ANY($3)
ORDER BY tx.height, tx.index, event.index
LIMIT 30 OFFSET 0
`).
WillReturnRows(eventRows).
WithArgs(indexValue, typeValue, pq.Array(hashValues))
// Arrange: Query
qry := query.NewEventQuery(
query.WithFilters(
NewFilter("event.index", indexValue),
FilterByEventType(typeValue),
FilterByEventTXs(hashValues...),
),
)
// Act
events, err := adapter.QueryEvents(ctx, qry)
// Assert
require.NoError(t, err, "expected no query errors on execution")
require.Len(t, events, 0)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestEventQueryWithEventAttrFilters(t *testing.T) {
// Arrange
db, mock := createMatchEqualSQLMock(t)
defer db.Close()
adapter := Adapter{db: db}
ctx := context.Background()
// Arrange: Database mocks
attrNameValue := "foo"
attrValue := int64(42)
eventRows := sqlmock.NewRows(eventFields)
mock.
ExpectQuery(`
SELECT DISTINCT events.*
FROM (
SELECT event.id, event.index, event.tx_hash, event.type, event.created_at
FROM event
INNER JOIN tx ON event.tx_hash = tx.hash
INNER JOIN attribute ON event.id = attribute.event_id
WHERE attribute.name = $1 AND attribute.name = $2 AND attribute.value::numeric = $3
ORDER BY tx.height, tx.index, event.index
) AS events
LIMIT 30 OFFSET 0
`).
WillReturnRows(eventRows).
WithArgs(attrNameValue, attrNameValue, attrValue)
// Arrange: Query
qry := query.NewEventQuery(
query.WithFilters(
NewFilter("attribute.name", attrNameValue),
FilterByEventAttrName(attrNameValue),
FilterByEventAttrValueInt(attrValue),
),
)
// Act
events, err := adapter.QueryEvents(ctx, qry)
// Assert
require.NoError(t, err, "expected no query errors on execution")
require.Len(t, events, 0)
require.NoError(t, mock.ExpectationsWereMet())
}
func createMatchEqualSQLMock(t *testing.T) (*sql.DB, sqlmock.Sqlmock) {
t.Helper()
db, mock, err := sqlmock.New(
sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual),
)
require.NoError(t, err)
return db, mock
}