...

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

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

     1  package metrics_test
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/cybertec-postgresql/pgwatch/v6/internal/log"
     8  	"github.com/cybertec-postgresql/pgwatch/v6/internal/metrics"
     9  	"github.com/jackc/pgx/v5/pgconn"
    10  	"github.com/pashagolub/pgxmock/v5"
    11  	"github.com/stretchr/testify/assert"
    12  )
    13  
    14  var ctx = log.WithLogger(context.Background(), log.NewNoopLogger())
    15  
    16  func AnyArgs(n int) []any {
    17  	args := make([]any, n)
    18  	for i := range args {
    19  		args[i] = pgxmock.AnyArg()
    20  	}
    21  	return args
    22  }
    23  
    24  func TestNewPostgresMetricReaderWriter(t *testing.T) {
    25  	a := assert.New(t)
    26  
    27  	t.Run("ConnectionError", func(*testing.T) {
    28  		pgrw, err := metrics.NewPostgresMetricReaderWriter(ctx, "postgres://user:pass@foohost:5432/db1")
    29  		a.Error(err)
    30  		a.Nil(pgrw)
    31  	})
    32  	t.Run("InvalidConnStr", func(*testing.T) {
    33  		pgrw, err := metrics.NewPostgresMetricReaderWriter(ctx, "invalid_connstr")
    34  		a.Error(err)
    35  		a.Nil(pgrw)
    36  	})
    37  }
    38  
    39  func TestNewPostgresMetricReaderWriterConn(t *testing.T) {
    40  	df := metrics.GetDefaultMetrics()
    41  	metricsCount := len(df.MetricDefs)
    42  	presetsCount := len(df.PresetDefs)
    43  
    44  	a := assert.New(t)
    45  	conn, err := pgxmock.NewPool()
    46  	a.NoError(err)
    47  
    48  	doesntExist := func() *pgxmock.Rows { return pgxmock.NewRows([]string{"exists"}).AddRow(false) }
    49  
    50  	t.Run("FullBoostrap", func(*testing.T) {
    51  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
    52  		conn.ExpectBegin()
    53  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
    54  		conn.ExpectBegin()
    55  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(metricsCount))
    56  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(presetsCount))
    57  		conn.ExpectCommit()
    58  		conn.ExpectCommit()
    59  		conn.ExpectPing()
    60  
    61  		readerWriter, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
    62  		a.NoError(err)
    63  		a.NotNil(readerWriter)
    64  		a.NoError(conn.ExpectationsWereMet())
    65  	})
    66  
    67  	t.Run("SchemaQueryFail", func(*testing.T) {
    68  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnError(assert.AnError)
    69  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
    70  		a.Error(err)
    71  		a.Nil(rw)
    72  		a.NoError(conn.ExpectationsWereMet())
    73  	})
    74  
    75  	t.Run("BeginFail", func(*testing.T) {
    76  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
    77  		conn.ExpectBegin().WillReturnError(assert.AnError)
    78  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
    79  		a.Error(err)
    80  		a.Nil(rw)
    81  		a.NoError(conn.ExpectationsWereMet())
    82  	})
    83  
    84  	t.Run("CreateSchemaFail", func(*testing.T) {
    85  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
    86  		conn.ExpectBegin()
    87  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnError(assert.AnError)
    88  		conn.ExpectRollback()
    89  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
    90  		a.Error(err)
    91  		a.Nil(rw)
    92  		a.NoError(conn.ExpectationsWereMet())
    93  	})
    94  
    95  	t.Run("WriteDefaultMetricsBeginFail", func(*testing.T) {
    96  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
    97  		conn.ExpectBegin()
    98  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
    99  		conn.ExpectBegin().WillReturnError(assert.AnError)
   100  		conn.ExpectRollback()
   101  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   102  		a.Error(err)
   103  		a.Nil(rw)
   104  		a.NoError(conn.ExpectationsWereMet())
   105  	})
   106  
   107  	t.Run("WriteInsertMetricsFail", func(*testing.T) {
   108  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
   109  		conn.ExpectBegin()
   110  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
   111  		conn.ExpectBegin()
   112  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnError(assert.AnError)
   113  		conn.ExpectRollback()
   114  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   115  		a.Error(err)
   116  		a.Nil(rw)
   117  		a.NoError(conn.ExpectationsWereMet())
   118  	})
   119  
   120  	t.Run("WriteInsertPresetsFail", func(*testing.T) {
   121  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
   122  		conn.ExpectBegin()
   123  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
   124  		conn.ExpectBegin()
   125  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(metricsCount))
   126  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnError(assert.AnError)
   127  		conn.ExpectRollback()
   128  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   129  		a.Error(err)
   130  		a.Nil(rw)
   131  		a.NoError(conn.ExpectationsWereMet())
   132  	})
   133  
   134  	t.Run("DefaultMetricsCommitFail", func(*testing.T) {
   135  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
   136  		conn.ExpectBegin()
   137  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
   138  		conn.ExpectBegin()
   139  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(metricsCount))
   140  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(presetsCount))
   141  		conn.ExpectCommit().WillReturnError(assert.AnError)
   142  		conn.ExpectRollback()
   143  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   144  		a.Error(err)
   145  		a.Nil(rw)
   146  		a.NoError(conn.ExpectationsWereMet())
   147  	})
   148  
   149  	t.Run("CommitFail", func(*testing.T) {
   150  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(doesntExist())
   151  		conn.ExpectBegin()
   152  		conn.ExpectExec("CREATE SCHEMA IF NOT EXISTS pgwatch").WillReturnResult(pgxmock.NewResult("CREATE", 1))
   153  		conn.ExpectBegin()
   154  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(metricsCount))
   155  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 1)).Times(uint(presetsCount))
   156  		conn.ExpectCommit()
   157  		conn.ExpectCommit().WillReturnError(assert.AnError)
   158  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   159  		a.Error(err)
   160  		a.Nil(rw)
   161  		a.NoError(conn.ExpectationsWereMet())
   162  	})
   163  
   164  	t.Run("SchemaExists", func(*testing.T) {
   165  		conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").WillReturnRows(pgxmock.NewRows([]string{"exists"}).AddRow(true))
   166  		conn.ExpectPing()
   167  		rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   168  		a.NoError(err)
   169  		a.NotNil(rw)
   170  		a.NoError(conn.ExpectationsWereMet())
   171  	})
   172  }
   173  
   174  func newTestReaderWriter(t *testing.T) (pgxmock.PgxPoolIface, metrics.ReaderWriter) {
   175  	t.Helper()
   176  	conn, err := pgxmock.NewPool()
   177  	if err != nil {
   178  		t.Fatalf("pgxmock.NewPool: %v", err)
   179  	}
   180  	conn.ExpectQuery(`SELECT EXISTS`).WithArgs("pgwatch").
   181  		WillReturnRows(pgxmock.NewRows([]string{"exists"}).AddRow(true))
   182  	conn.ExpectPing()
   183  	rw, err := metrics.NewPostgresMetricReaderWriterConn(ctx, conn)
   184  	if err != nil {
   185  		t.Fatalf("NewPostgresMetricReaderWriterConn: %v", err)
   186  	}
   187  	return conn, rw
   188  }
   189  
   190  func TestMetricsToPostgres(t *testing.T) {
   191  	conn, rw := newTestReaderWriter(t)
   192  
   193  	metricsRows := func() *pgxmock.Rows {
   194  		return pgxmock.NewRows([]string{"name", "sqls", "init_sql", "description", "node_status", "gauges", "is_instance_level", "storage_name"}).
   195  			AddRow("test", metrics.SQLs{11: "select"}, "init", "desc", "primary", []string{"*"}, true, "storage")
   196  	}
   197  	presetRows := func() *pgxmock.Rows {
   198  		return pgxmock.NewRows([]string{"name", "description", "metrics"}).
   199  			AddRow("test", "desc", metrics.MetricIntervals{"metric": 30})
   200  	}
   201  
   202  	t.Run("GetMetrics", func(t *testing.T) {
   203  		conn.ExpectQuery(`SELECT.+FROM.+metric`).WillReturnRows(metricsRows())
   204  		conn.ExpectQuery(`SELECT.+FROM.+preset`).WillReturnRows(presetRows())
   205  		m, err := rw.GetMetrics()
   206  		assert.NoError(t, err)
   207  		assert.Len(t, m.MetricDefs, 1)
   208  	})
   209  
   210  	t.Run("GetMetricsFail", func(t *testing.T) {
   211  		conn.ExpectQuery(`SELECT.+FROM.+metric`).WillReturnError(assert.AnError)
   212  		_, err := rw.GetMetrics()
   213  		assert.Error(t, err)
   214  	})
   215  
   216  	t.Run("GetPresetsFail", func(t *testing.T) {
   217  		conn.ExpectQuery(`SELECT.+FROM.+metric`).WillReturnRows(metricsRows())
   218  		conn.ExpectQuery(`SELECT.+FROM.+preset`).WillReturnError(assert.AnError)
   219  		_, err := rw.GetMetrics()
   220  		assert.Error(t, err)
   221  	})
   222  
   223  	t.Run("GetMetricsScanFail", func(t *testing.T) {
   224  		conn.ExpectQuery(`SELECT.+FROM.+metric`).WillReturnRows(metricsRows().RowError(0, assert.AnError))
   225  		_, err := rw.GetMetrics()
   226  		assert.Error(t, err)
   227  	})
   228  
   229  	t.Run("GetPresetsScanFail", func(t *testing.T) {
   230  		conn.ExpectQuery(`SELECT.+FROM.+metric`).WillReturnRows(metricsRows())
   231  		conn.ExpectQuery(`SELECT.+FROM.+preset`).WillReturnRows(presetRows().RowError(0, assert.AnError))
   232  		_, err := rw.GetMetrics()
   233  		assert.Error(t, err)
   234  	})
   235  
   236  	t.Run("WriteMetrics", func(t *testing.T) {
   237  		conn.ExpectBegin().WillReturnError(assert.AnError)
   238  		assert.Error(t, rw.WriteMetrics(&metrics.Metrics{}))
   239  	})
   240  
   241  	t.Run("DeleteMetric", func(t *testing.T) {
   242  		conn.ExpectExec(`DELETE.+metric`).WithArgs("test").WillReturnResult(pgxmock.NewResult("DELETE", 1))
   243  		assert.NoError(t, rw.DeleteMetric("test"))
   244  	})
   245  
   246  	t.Run("UpdateMetric", func(t *testing.T) {
   247  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("UPDATE", 1))
   248  		assert.NoError(t, rw.UpdateMetric("test", metrics.Metric{}))
   249  	})
   250  
   251  	t.Run("FailUpdateMetric", func(t *testing.T) {
   252  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("UPDATE", 0))
   253  		assert.ErrorIs(t, rw.UpdateMetric("test", metrics.Metric{}), metrics.ErrMetricNotFound)
   254  	})
   255  
   256  	t.Run("DeletePreset", func(t *testing.T) {
   257  		conn.ExpectExec(`DELETE.+preset`).WithArgs("test").WillReturnResult(pgxmock.NewResult("DELETE", 1))
   258  		assert.NoError(t, rw.DeletePreset("test"))
   259  	})
   260  
   261  	t.Run("UpdatePreset", func(t *testing.T) {
   262  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 1))
   263  		assert.NoError(t, rw.UpdatePreset("test", metrics.Preset{}))
   264  	})
   265  
   266  	t.Run("FailUpdatePreset", func(t *testing.T) {
   267  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 0))
   268  		assert.ErrorIs(t, rw.UpdatePreset("test", metrics.Preset{}), metrics.ErrPresetNotFound)
   269  	})
   270  
   271  	t.Run("DeleteMetricError", func(t *testing.T) {
   272  		conn.ExpectExec(`DELETE.+metric`).WithArgs("test").WillReturnError(assert.AnError)
   273  		assert.Error(t, rw.DeleteMetric("test"))
   274  	})
   275  
   276  	t.Run("UpdateMetricExecError", func(t *testing.T) {
   277  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnError(assert.AnError)
   278  		assert.Error(t, rw.UpdateMetric("test", metrics.Metric{}))
   279  	})
   280  
   281  	t.Run("DeletePresetError", func(t *testing.T) {
   282  		conn.ExpectExec(`DELETE.+preset`).WithArgs("test").WillReturnError(assert.AnError)
   283  		assert.Error(t, rw.DeletePreset("test"))
   284  	})
   285  
   286  	t.Run("UpdatePresetExecError", func(t *testing.T) {
   287  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnError(assert.AnError)
   288  		assert.Error(t, rw.UpdatePreset("test", metrics.Preset{}))
   289  	})
   290  
   291  	assert.NoError(t, conn.ExpectationsWereMet())
   292  }
   293  
   294  func TestCreateMetric(t *testing.T) {
   295  	conn, rw := newTestReaderWriter(t)
   296  
   297  	t.Run("Success", func(t *testing.T) {
   298  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnResult(pgxmock.NewResult("INSERT", 1))
   299  		assert.NoError(t, rw.CreateMetric("new_metric", metrics.Metric{}))
   300  	})
   301  
   302  	t.Run("Duplicate", func(t *testing.T) {
   303  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnError(&pgconn.PgError{Code: "23505"})
   304  		assert.ErrorIs(t, rw.CreateMetric("existing_metric", metrics.Metric{}), metrics.ErrMetricExists)
   305  	})
   306  
   307  	t.Run("ExecError", func(t *testing.T) {
   308  		conn.ExpectExec(`INSERT.+metric`).WithArgs(AnyArgs(8)...).WillReturnError(assert.AnError)
   309  		err := rw.CreateMetric("fail_metric", metrics.Metric{})
   310  		assert.Error(t, err)
   311  		assert.NotErrorIs(t, err, metrics.ErrMetricExists)
   312  	})
   313  
   314  	assert.NoError(t, conn.ExpectationsWereMet())
   315  }
   316  
   317  func TestCreatePreset(t *testing.T) {
   318  	conn, rw := newTestReaderWriter(t)
   319  
   320  	t.Run("Success", func(t *testing.T) {
   321  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnResult(pgxmock.NewResult("INSERT", 1))
   322  		assert.NoError(t, rw.CreatePreset("new_preset", metrics.Preset{}))
   323  	})
   324  
   325  	t.Run("Duplicate", func(t *testing.T) {
   326  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnError(&pgconn.PgError{Code: "23505"})
   327  		assert.ErrorIs(t, rw.CreatePreset("existing_preset", metrics.Preset{}), metrics.ErrPresetExists)
   328  	})
   329  
   330  	t.Run("ExecError", func(t *testing.T) {
   331  		conn.ExpectExec(`INSERT.+preset`).WithArgs(AnyArgs(3)...).WillReturnError(assert.AnError)
   332  		err := rw.CreatePreset("fail_preset", metrics.Preset{})
   333  		assert.Error(t, err)
   334  		assert.NotErrorIs(t, err, metrics.ErrPresetExists)
   335  	})
   336  
   337  	assert.NoError(t, conn.ExpectationsWereMet())
   338  }
   339