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