|
|
- package psql
-
- import (
- "context"
- "database/sql"
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "testing"
- "time"
-
- sq "github.com/Masterminds/squirrel"
- schema "github.com/adlio/schema"
- proto "github.com/gogo/protobuf/proto"
- _ "github.com/lib/pq"
- dockertest "github.com/ory/dockertest"
- "github.com/ory/dockertest/docker"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- abci "github.com/tendermint/tendermint/abci/types"
- "github.com/tendermint/tendermint/state/indexer"
- "github.com/tendermint/tendermint/types"
- )
-
- var db *sql.DB
- var resource *dockertest.Resource
- var chainID = "test-chainID"
-
- var (
- user = "postgres"
- password = "secret"
- port = "5432"
- dsn = "postgres://%s:%s@localhost:%s/%s?sslmode=disable"
- dbName = "postgres"
- )
-
- func TestType(t *testing.T) {
- pool, err := setupDB(t)
- require.NoError(t, err)
-
- psqlSink := &EventSink{store: db, chainID: chainID}
- assert.Equal(t, indexer.PSQL, psqlSink.Type())
- require.NoError(t, teardown(t, pool))
- }
-
- func TestBlockFuncs(t *testing.T) {
- pool, err := setupDB(t)
- require.NoError(t, err)
-
- indexer := &EventSink{store: db, chainID: chainID}
- require.NoError(t, indexer.IndexBlockEvents(getTestBlockHeader()))
-
- r, err := verifyBlock(1)
- assert.True(t, r)
- require.NoError(t, err)
-
- r, err = verifyBlock(2)
- assert.False(t, r)
- require.NoError(t, err)
-
- r, err = indexer.HasBlock(1)
- assert.False(t, r)
- assert.Equal(t, errors.New("hasBlock is not supported via the postgres event sink"), err)
-
- r, err = indexer.HasBlock(2)
- assert.False(t, r)
- assert.Equal(t, errors.New("hasBlock is not supported via the postgres event sink"), err)
-
- r2, err := indexer.SearchBlockEvents(context.TODO(), nil)
- assert.Nil(t, r2)
- assert.Equal(t, errors.New("block search is not supported via the postgres event sink"), err)
-
- require.NoError(t, verifyTimeStamp(TableEventBlock))
-
- // try to insert the duplicate block events.
- err = indexer.IndexBlockEvents(getTestBlockHeader())
- require.NoError(t, err)
-
- require.NoError(t, teardown(t, pool))
- }
-
- func TestTxFuncs(t *testing.T) {
- pool, err := setupDB(t)
- assert.Nil(t, err)
-
- indexer := &EventSink{store: db, chainID: chainID}
-
- txResult := txResultWithEvents([]abci.Event{
- {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}},
- {Type: "account", Attributes: []abci.EventAttribute{{Key: "owner", Value: "Ivan", Index: true}}},
- {Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}},
- })
- err = indexer.IndexTxEvents([]*abci.TxResult{txResult})
- require.NoError(t, err)
-
- tx, err := verifyTx(types.Tx(txResult.Tx).Hash())
- require.NoError(t, err)
- assert.Equal(t, txResult, tx)
-
- require.NoError(t, verifyTimeStamp(TableEventTx))
- require.NoError(t, verifyTimeStamp(TableResultTx))
-
- tx, err = indexer.GetTxByHash(types.Tx(txResult.Tx).Hash())
- assert.Nil(t, tx)
- assert.Equal(t, errors.New("getTxByHash is not supported via the postgres event sink"), err)
-
- r2, err := indexer.SearchTxEvents(context.TODO(), nil)
- assert.Nil(t, r2)
- assert.Equal(t, errors.New("tx search is not supported via the postgres event sink"), err)
-
- // try to insert the duplicate tx events.
- err = indexer.IndexTxEvents([]*abci.TxResult{txResult})
- require.NoError(t, err)
-
- assert.Nil(t, teardown(t, pool))
- }
-
- func TestStop(t *testing.T) {
- pool, err := setupDB(t)
- require.NoError(t, err)
-
- indexer := &EventSink{store: db}
- require.NoError(t, indexer.Stop())
-
- defer db.Close()
- require.NoError(t, pool.Purge(resource))
- }
-
- func getTestBlockHeader() types.EventDataNewBlockHeader {
- return types.EventDataNewBlockHeader{
- Header: types.Header{Height: 1},
- ResultBeginBlock: abci.ResponseBeginBlock{
- Events: []abci.Event{
- {
- Type: "begin_event",
- Attributes: []abci.EventAttribute{
- {
- Key: "proposer",
- Value: "FCAA001",
- Index: true,
- },
- },
- },
- },
- },
- ResultEndBlock: abci.ResponseEndBlock{
- Events: []abci.Event{
- {
- Type: "end_event",
- Attributes: []abci.EventAttribute{
- {
- Key: "foo",
- Value: "100",
- Index: true,
- },
- },
- },
- },
- },
- }
- }
-
- func readSchema() ([]*schema.Migration, error) {
-
- filename := "schema.sql"
- contents, err := ioutil.ReadFile(filename)
- if err != nil {
- return nil, fmt.Errorf("failed to read sql file from '%s': %w", filename, err)
- }
-
- mg := &schema.Migration{}
- mg.ID = time.Now().Local().String() + " db schema"
- mg.Script = string(contents)
- return append([]*schema.Migration{}, mg), nil
- }
-
- func resetDB(t *testing.T) {
- q := "DROP TABLE IF EXISTS block_events,tx_events,tx_results"
- _, err := db.Exec(q)
-
- require.NoError(t, err)
-
- q = "DROP TYPE IF EXISTS block_event_type"
- _, err = db.Exec(q)
- require.NoError(t, err)
- }
-
- func txResultWithEvents(events []abci.Event) *abci.TxResult {
- tx := types.Tx("HELLO WORLD")
- return &abci.TxResult{
- Height: 1,
- Index: 0,
- Tx: tx,
- Result: abci.ResponseDeliverTx{
- Data: []byte{0},
- Code: abci.CodeTypeOK,
- Log: "",
- Events: events,
- },
- }
- }
-
- func verifyTx(hash []byte) (*abci.TxResult, error) {
- join := fmt.Sprintf("%s ON %s.id = tx_result_id", TableEventTx, TableResultTx)
- sqlStmt := sq.
- Select("tx_result", fmt.Sprintf("%s.id", TableResultTx), "tx_result_id", "hash", "chain_id").
- Distinct().From(TableResultTx).
- InnerJoin(join).
- Where(fmt.Sprintf("hash = $1 AND chain_id = '%s'", chainID), fmt.Sprintf("%X", hash))
-
- rows, err := sqlStmt.RunWith(db).Query()
- if err != nil {
- return nil, err
- }
-
- defer rows.Close()
-
- if rows.Next() {
- var txResult []byte
- var txResultID, txid int
- var h, cid string
- err = rows.Scan(&txResult, &txResultID, &txid, &h, &cid)
- if err != nil {
- return nil, nil
- }
-
- msg := new(abci.TxResult)
- err = proto.Unmarshal(txResult, msg)
- if err != nil {
- return nil, err
- }
-
- return msg, err
- }
-
- // No result
- return nil, nil
- }
-
- func verifyTimeStamp(tb string) error {
-
- // We assume the tx indexing time would not exceed 2 second from now
- sqlStmt := sq.
- Select(fmt.Sprintf("%s.created_at", tb)).
- Distinct().From(tb).
- Where(fmt.Sprintf("%s.created_at >= $1", tb), time.Now().Add(-2*time.Second))
-
- rows, err := sqlStmt.RunWith(db).Query()
- if err != nil {
- return err
- }
-
- defer rows.Close()
-
- if rows.Next() {
- var ts string
- err = rows.Scan(&ts)
- if err != nil {
- return err
- }
-
- return nil
- }
-
- return errors.New("no result")
- }
-
- func verifyBlock(h int64) (bool, error) {
- sqlStmt := sq.
- Select("height").
- Distinct().
- From(TableEventBlock).
- Where(fmt.Sprintf("height = %d", h))
- rows, err := sqlStmt.RunWith(db).Query()
- if err != nil {
- return false, err
- }
-
- defer rows.Close()
-
- if !rows.Next() {
- return false, nil
- }
-
- sqlStmt = sq.
- Select("type, height", "chain_id").
- Distinct().
- From(TableEventBlock).
- Where(fmt.Sprintf("height = %d AND type = '%s' AND chain_id = '%s'", h, types.EventTypeBeginBlock, chainID))
-
- rows, err = sqlStmt.RunWith(db).Query()
- if err != nil {
- return false, err
- }
- defer rows.Close()
-
- if !rows.Next() {
- return false, nil
- }
-
- sqlStmt = sq.
- Select("type, height").
- Distinct().
- From(TableEventBlock).
- Where(fmt.Sprintf("height = %d AND type = '%s'", h, types.EventTypeEndBlock))
- rows, err = sqlStmt.RunWith(db).Query()
-
- if err != nil {
- return false, err
- }
- defer rows.Close()
-
- return rows.Next(), nil
- }
-
- func setupDB(t *testing.T) (*dockertest.Pool, error) {
- t.Helper()
- pool, err := dockertest.NewPool(os.Getenv("DOCKER_URL"))
-
- require.NoError(t, err)
-
- resource, err = pool.RunWithOptions(&dockertest.RunOptions{
- Repository: DriverName,
- Tag: "13",
- Env: []string{
- "POSTGRES_USER=" + user,
- "POSTGRES_PASSWORD=" + password,
- "POSTGRES_DB=" + dbName,
- "listen_addresses = '*'",
- },
- ExposedPorts: []string{port},
- }, func(config *docker.HostConfig) {
- // set AutoRemove to true so that stopped container goes away by itself
- config.AutoRemove = true
- config.RestartPolicy = docker.RestartPolicy{
- Name: "no",
- }
- })
-
- require.NoError(t, err)
-
- // Set the container to expire in a minute to avoid orphaned containers
- // hanging around
- _ = resource.Expire(60)
-
- conn := fmt.Sprintf(dsn, user, password, resource.GetPort(port+"/tcp"), dbName)
-
- if err = pool.Retry(func() error {
- var err error
-
- _, db, err = NewEventSink(conn, chainID)
-
- if err != nil {
- return err
- }
-
- return db.Ping()
- }); err != nil {
- require.NoError(t, err)
- }
-
- resetDB(t)
-
- sm, err := readSchema()
- assert.Nil(t, err)
- assert.Nil(t, schema.NewMigrator().Apply(db, sm))
- return pool, nil
- }
-
- func teardown(t *testing.T, pool *dockertest.Pool) error {
- t.Helper()
- // When you're done, kill and remove the container
- assert.Nil(t, pool.Purge(resource))
- return db.Close()
- }
|