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
262 a.NoError(conn.ExpectationsWereMet())
263 }
264
265
266