...

Source file src/github.com/cybertec-postgresql/pgwatch/v3/internal/metrics/postgres_test.go

Documentation: github.com/cybertec-postgresql/pgwatch/v3/internal/metrics

     1  package metrics_test
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/cybertec-postgresql/pgwatch/v3/internal/metrics"
     8  	"github.com/pashagolub/pgxmock/v4"
     9  	"github.com/stretchr/testify/assert"
    10  )
    11  
    12  func AnyArgs(n int) []any {
    13  	args := make([]any, n)
    14  	for i := range args {
    15  		args[i] = pgxmock.AnyArg()
    16  	}
    17  	return args
    18  }
    19  
    20  func TestNewPostgresMetricReaderWriter(t *testing.T) {
    21  	a := assert.New(t)
    22  	ctx := context.Background()
    23  	t.Run("ConnectionError", func(*testing.T) {
    24  		pgrw, err := metrics.NewPostgresMetricReaderWriter(ctx, "postgres://user:pass@foohost:5432/db1")
    25  		a.Error(err)
    26  		a.Nil(pgrw)
    27  	})
    28  	t.Run("InvalidConnStr", func(*testing.T) {
    29  		pgrw, err := metrics.NewPostgresMetricReaderWriter(ctx, "invalid_connstr")
    30  		a.Error(err)
    31  		a.Nil(pgrw)
    32  	})
    33  }
    34  
    35  func TestNewPostgresMetricReaderWriterConn(t *testing.T) {
    36  	df := metrics.GetDefaultMetrics()
    37  	metricsCount := len(df.MetricDefs)
    38  	presetsCount := len(df.PresetDefs)
    39  
    40  	a := assert.New(t)
    41  	conn, err := pgxmock.NewPool()
    42  	a.NoError(err)
    43  	ctx := context.Background()
    44  
    45  	doesntExist := func() *pgxmock.Rows { return pgxmock.NewRows([]string{"exists"}).AddRow(false) }
    46  
    47  	t.Run("FullBoostrap", func(*testing.T) {
    48  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
    49  		conn.ExpectBegin()
    50  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
    51  		conn.ExpectBegin()
    52  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(metricsCount))
    53  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(presetsCount))
    54  		conn.ExpectCommit()
    55  		conn.ExpectCommit()
    56  		conn.ExpectPing()
    57  
    58  		readerWriter, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
    59  		a.NoError(err)
    60  		a.NotNil(readerWriter)
    61  		a.NoError(conn.ExpectationsWereMet())
    62  	})
    63  
    64  	t.Run("SchemaQueryFail", func(*testing.T) {
    65  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnError(assert.AnError)
    66  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
    67  		a.Error(err)
    68  		a.Nil(rw)
    69  		a.NoError(conn.ExpectationsWereMet())
    70  	})
    71  
    72  	t.Run("BeginFail", func(*testing.T) {
    73  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
    74  		conn.ExpectBegin().WillReturnError(assert.AnError)
    75  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
    76  		a.Error(err)
    77  		a.Nil(rw)
    78  		a.NoError(conn.ExpectationsWereMet())
    79  	})
    80  
    81  	t.Run("CreateSchemaFail", func(*testing.T) {
    82  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
    83  		conn.ExpectBegin()
    84  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnError(assert.AnError)
    85  		conn.ExpectRollback()
    86  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
    87  		a.Error(err)
    88  		a.Nil(rw)
    89  		a.NoError(conn.ExpectationsWereMet())
    90  	})
    91  
    92  	t.Run("WriteDefaultMetricsBeginFail", func(*testing.T) {
    93  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
    94  		conn.ExpectBegin()
    95  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
    96  		conn.ExpectBegin().WillReturnError(assert.AnError)
    97  		conn.ExpectRollback()
    98  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
    99  		a.Error(err)
   100  		a.Nil(rw)
   101  		a.NoError(conn.ExpectationsWereMet())
   102  	})
   103  
   104  	t.Run("WriteInsertMetricsFail", func(*testing.T) {
   105  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
   106  		conn.ExpectBegin()
   107  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
   108  		conn.ExpectBegin()
   109  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnError(assert.AnError)
   110  		conn.ExpectRollback()
   111  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   112  		a.Error(err)
   113  		a.Nil(rw)
   114  		a.NoError(conn.ExpectationsWereMet())
   115  	})
   116  
   117  	t.Run("WriteInsertPresetsFail", func(*testing.T) {
   118  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
   119  		conn.ExpectBegin()
   120  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
   121  		conn.ExpectBegin()
   122  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(metricsCount))
   123  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnError(assert.AnError)
   124  		conn.ExpectRollback()
   125  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   126  		a.Error(err)
   127  		a.Nil(rw)
   128  		a.NoError(conn.ExpectationsWereMet())
   129  	})
   130  
   131  	t.Run("DefaultMetricsCommitFail", func(*testing.T) {
   132  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
   133  		conn.ExpectBegin()
   134  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
   135  		conn.ExpectBegin()
   136  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(metricsCount))
   137  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(presetsCount))
   138  		conn.ExpectCommit().WillReturnError(assert.AnError)
   139  		conn.ExpectRollback()
   140  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   141  		a.Error(err)
   142  		a.Nil(rw)
   143  		a.NoError(conn.ExpectationsWereMet())
   144  	})
   145  
   146  	t.Run("CommitFail", func(*testing.T) {
   147  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
   148  		conn.ExpectBegin()
   149  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
   150  		conn.ExpectBegin()
   151  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(metricsCount))
   152  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(presetsCount))
   153  		conn.ExpectCommit()
   154  		conn.ExpectCommit().WillReturnError(assert.AnError)
   155  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   156  		a.Error(err)
   157  		a.Nil(rw)
   158  		a.NoError(conn.ExpectationsWereMet())
   159  	})
   160  }
   161  
   162  func TestMetricsToPostgres(t *testing.T) {
   163  	a := assert.New(t)
   164  	conn, err := pgxmock.NewPool()
   165  	a.NoError(err)
   166  	ctx := context.Background()
   167  
   168  	conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(pgxmock.NewRows([]string{"exists"}).AddRow(true))
   169  	conn.ExpectPing()
   170  
   171  	readerWriter, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   172  	a.NoError(err)
   173  	a.NotNil(readerWriter)
   174  
   175  	metricsRows := func() *pgxmock.Rows {
   176  		return pgxmock.NewRows([]string{"name", "sqls", "init_sql", "description", "node_status", "gauges", "is_instance_level", "storage_name"}).
   177  			AddRow("test", metrics.SQLs{11: "select"}, "init", "desc", "primary", []string{"*"}, true, "storage")
   178  	}
   179  	presetRows := func() *pgxmock.Rows {
   180  		return pgxmock.NewRows([]string{"name", "description", "metrics"}).
   181  			AddRow("test", "desc", map[string]float64{"metric": 30})
   182  	}
   183  
   184  	t.Run("GetMetrics", func(*testing.T) {
   185  		conn.ExpectQuery(`SELECT.+FROM.+metric`).WillReturnRows(metricsRows())
   186  		conn.ExpectQuery(`SELECT.+FROM.+preset`).WillReturnRows(presetRows())
   187  
   188  		m, err := readerWriter.GetMetrics()
   189  		a.NoError(err)
   190  		a.Len(m.MetricDefs, 1)
   191  	})
   192  
   193  	t.Run("GetMetricsFail", func(*testing.T) {
   194  		conn.ExpectQuery(`SELECT.+FROM.+metric`).WillReturnError(assert.AnError)
   195  		_, err = readerWriter.GetMetrics()
   196  		a.Error(err)
   197  	})
   198  
   199  	t.Run("GetPresetsFail", func(*testing.T) {
   200  		conn.ExpectQuery(`SELECT.+FROM.+metric`).WillReturnRows(metricsRows())
   201  		conn.ExpectQuery(`SELECT.+FROM.+preset`).WillReturnError(assert.AnError)
   202  		_, err = readerWriter.GetMetrics()
   203  		a.Error(err)
   204  	})
   205  
   206  	t.Run("GetMetricsScanFail", func(*testing.T) {
   207  		conn.ExpectQuery(`SELECT.+FROM.+metric`).WillReturnRows(metricsRows().RowError(0, assert.AnError))
   208  		_, err = readerWriter.GetMetrics()
   209  		a.Error(err)
   210  	})
   211  
   212  	t.Run("GetPresetsScanFail", func(*testing.T) {
   213  		conn.ExpectQuery(`SELECT.+FROM.+metric`).WillReturnRows(metricsRows())
   214  		conn.ExpectQuery(`SELECT.+FROM.+preset`).WillReturnRows(presetRows().RowError(0, assert.AnError))
   215  		_, err = readerWriter.GetMetrics()
   216  		a.Error(err)
   217  	})
   218  
   219  	t.Run("WriteMetrics", func(*testing.T) {
   220  		conn.ExpectBegin().WillReturnError(assert.AnError)
   221  		err = readerWriter.WriteMetrics(&metrics.Metrics{})
   222  		a.Error(err)
   223  	})
   224  
   225  	t.Run("DeleteMetric", func(*testing.T) {
   226  		conn.ExpectExec(`DELETE.+metric`).WithArgs("test").WillReturnResult(pgxmock.NewResult("DELETE", 1))
   227  		err = readerWriter.DeleteMetric("test")
   228  		a.NoError(err)
   229  	})
   230  
   231  	t.Run("UpdateMetric", func(*testing.T) {
   232  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("UPDATE", 1))
   233  		err = readerWriter.UpdateMetric("test", metrics.Metric{})
   234  		a.NoError(err)
   235  	})
   236  
   237  	t.Run("FailUpdateMetric", func(*testing.T) {
   238  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("UPDATE", 0))
   239  		err = readerWriter.UpdateMetric("test", metrics.Metric{})
   240  		a.ErrorIs(err, metrics.ErrMetricNotFound)
   241  	})
   242  
   243  	t.Run("DeletePreset", func(*testing.T) {
   244  		conn.ExpectExec(`DELETE.+preset`).WithArgs("test").WillReturnResult(pgxmock.NewResult("DELETE", 1))
   245  		err = readerWriter.DeletePreset("test")
   246  		a.NoError(err)
   247  	})
   248  
   249  	t.Run("UpdatePreset", func(*testing.T) {
   250  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 1))
   251  		err = readerWriter.UpdatePreset("test", metrics.Preset{})
   252  		a.NoError(err)
   253  	})
   254  
   255  	t.Run("FailUpdatePreset", func(*testing.T) {
   256  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 0))
   257  		err = readerWriter.UpdatePreset("test", metrics.Preset{})
   258  		a.ErrorIs(err, metrics.ErrPresetNotFound)
   259  	})
   260  
   261  	// check all expectations were met
   262  	a.NoError(conn.ExpectationsWereMet())
   263  }
   264  
   265  // Additional tests for GetMetrics, WriteMetrics, DeleteMetric, UpdateMetric, DeletePreset, and UpdatePreset follow a similar pattern.
   266