1 package sources_test
2
3 import (
4 "errors"
5 "testing"
6
7 "github.com/pashagolub/pgxmock/v4"
8 "github.com/stretchr/testify/assert"
9
10 "github.com/cybertec-postgresql/pgwatch/v3/internal/sources"
11 )
12
13 func TestNewPostgresSourcesReaderWriter(t *testing.T) {
14 a := assert.New(t)
15 t.Run("ConnectionError", func(*testing.T) {
16 pgrw, err := sources.NewPostgresSourcesReaderWriter(ctx, "postgres://user:pass@foohost:5432/db1")
17 a.Error(err)
18 a.NotNil(t, pgrw)
19 })
20 t.Run("InvalidConnStr", func(*testing.T) {
21 pgrw, err := sources.NewPostgresSourcesReaderWriter(ctx, "invalid_connstr")
22 a.Error(err)
23 a.Nil(pgrw)
24 })
25 }
26
27 func TestNewPostgresSourcesReaderWriterConn(t *testing.T) {
28 a := assert.New(t)
29 conn, err := pgxmock.NewPool()
30 a.NoError(err)
31 conn.ExpectPing()
32
33 pgrw, err := sources.NewPostgresSourcesReaderWriterConn(ctx, conn)
34 a.NoError(err)
35 a.NotNil(t, pgrw)
36 a.NoError(conn.ExpectationsWereMet())
37 }
38
39 func TestGetMonitoredDatabases(t *testing.T) {
40 a := assert.New(t)
41 conn, err := pgxmock.NewPool()
42 a.NoError(err)
43 conn.ExpectPing()
44 conn.ExpectQuery(`select \/\* pgwatch_generated \*\/`).WillReturnRows(pgxmock.NewRows([]string{
45 "name", "group", "dbtype", "connstr", "config", "config_standby", "preset_config",
46 "preset_config_standby", "include_pattern", "exclude_pattern",
47 "custom_tags", "host_config", "only_if_master", "is_enabled",
48 }).AddRow(
49 "db1", "group1", sources.Kind("postgres"), "postgres://user:pass@localhost:5432/db1",
50 map[string]float64{"metric": 60}, map[string]float64{"standby_metric": 60}, "exhaustive", "exhaustive",
51 ".*", `\_.+`, map[string]string{"tag": "value"}, nil, true, true,
52 ))
53 pgrw, err := sources.NewPostgresSourcesReaderWriterConn(ctx, conn)
54 a.NoError(err)
55
56 dbs, err := pgrw.GetSources()
57 a.NoError(err)
58 a.Len(dbs, 1)
59 a.NoError(conn.ExpectationsWereMet())
60
61
62 conn.ExpectQuery(`select \/\* pgwatch_generated \*\/`).WillReturnError(errors.New("failed query"))
63 dbs, err = pgrw.GetSources()
64 a.Error(err)
65 a.Nil(dbs)
66 a.NoError(conn.ExpectationsWereMet())
67 }
68
69 func TestSyncFromReader(t *testing.T) {
70 a := assert.New(t)
71 conn, err := pgxmock.NewPool()
72 a.NoError(err)
73 conn.ExpectPing()
74 conn.ExpectQuery(`select \/\* pgwatch_generated \*\/`).WillReturnRows(pgxmock.NewRows([]string{
75 "name", "group", "dbtype", "connstr", "config", "config_standby", "preset_config",
76 "preset_config_standby", "include_pattern", "exclude_pattern",
77 "custom_tags", "host_config", "only_if_master", "is_enabled",
78 }).AddRow(
79 "db1", "group1", sources.Kind("postgres"), "postgres://user:pass@localhost:5432/db1",
80 map[string]float64{"metric": 60}, map[string]float64{"standby_metric": 60}, "exhaustive", "exhaustive",
81 ".*", `\_.+`, map[string]string{"tag": "value"}, nil, true, true,
82 ))
83 pgrw, err := sources.NewPostgresSourcesReaderWriterConn(ctx, conn)
84 a.NoError(err)
85
86 md := &sources.SourceConn{}
87 md.Name = "db1"
88 dbs := sources.SourceConns{md}
89 dbs, err = dbs.SyncFromReader(pgrw)
90 a.NoError(err)
91 a.Len(dbs, 1)
92 a.NoError(conn.ExpectationsWereMet())
93 }
94
95 func TestDeleteDatabase(t *testing.T) {
96 a := assert.New(t)
97 conn, err := pgxmock.NewPool()
98 a.NoError(err)
99 conn.ExpectPing()
100 conn.ExpectExec(`delete from pgwatch\.source where name = \$1`).WithArgs("db1").WillReturnResult(pgxmock.NewResult("DELETE", 1))
101 pgrw, err := sources.NewPostgresSourcesReaderWriterConn(ctx, conn)
102 a.NoError(err)
103
104 err = pgrw.DeleteSource("db1")
105 a.NoError(err)
106 a.NoError(conn.ExpectationsWereMet())
107 }
108
109 func TestUpdateDatabase(t *testing.T) {
110 a := assert.New(t)
111 conn, err := pgxmock.NewPool()
112 a.NoError(err)
113
114 md := sources.Source{
115 Name: "db1",
116 Group: "group1",
117 Kind: sources.Kind("postgres"),
118 ConnStr: "postgres://user:pass@localhost:5432/db1",
119 Metrics: map[string]float64{"metric": 60},
120 MetricsStandby: map[string]float64{"standby_metric": 60},
121 IncludePattern: ".*",
122 ExcludePattern: `\_.+`,
123 CustomTags: map[string]string{"tag": "value"},
124 }
125 conn.ExpectPing()
126 conn.ExpectExec(`insert into pgwatch\.source`).WithArgs(
127 md.Name, md.Group, md.Kind,
128 md.ConnStr, `{"metric":60}`, `{"standby_metric":60}`,
129 md.PresetMetrics, md.PresetMetricsStandby,
130 md.IncludePattern, md.ExcludePattern, `{"tag":"value"}`,
131 nil, md.OnlyIfMaster,
132 ).WillReturnResult(pgxmock.NewResult("UPDATE", 1))
133
134 pgrw, err := sources.NewPostgresSourcesReaderWriterConn(ctx, conn)
135 a.NoError(err)
136 err = pgrw.UpdateSource(md)
137 a.NoError(err)
138 a.NoError(conn.ExpectationsWereMet())
139 }
140
141 func TestWriteMonitoredDatabases(t *testing.T) {
142 var (
143 pgrw sources.ReaderWriter
144 err error
145 )
146 a := assert.New(t)
147 conn, err := pgxmock.NewPool()
148 a.NoError(err)
149 md := sources.Source{
150 Name: "db1",
151 Group: "group1",
152 Kind: sources.Kind("postgres"),
153 ConnStr: "postgres://user:pass@localhost:5432/db1",
154 Metrics: map[string]float64{"metric": 60},
155 MetricsStandby: map[string]float64{"standby_metric": 60},
156 IncludePattern: ".*",
157 ExcludePattern: `\_.+`,
158 CustomTags: map[string]string{"tag": "value"},
159 }
160 mds := sources.Sources{md}
161
162 t.Run("happy path", func(*testing.T) {
163 conn.ExpectPing()
164 conn.ExpectBegin()
165 conn.ExpectExec(`truncate pgwatch\.source`).WillReturnResult(pgxmock.NewResult("TRUNCATE", 1))
166 conn.ExpectExec(`insert into pgwatch\.source`).WithArgs(
167 md.Name, md.Group, md.Kind,
168 md.ConnStr, `{"metric":60}`, `{"standby_metric":60}`, md.PresetMetrics, md.PresetMetricsStandby,
169 md.IncludePattern, md.ExcludePattern, `{"tag":"value"}`,
170 nil, md.OnlyIfMaster,
171 ).WillReturnResult(pgxmock.NewResult("INSERT", 1))
172 conn.ExpectCommit()
173 conn.ExpectRollback()
174
175 pgrw, err = sources.NewPostgresSourcesReaderWriterConn(ctx, conn)
176 a.NoError(err)
177 err = pgrw.WriteSources(mds)
178 a.NoError(err)
179 a.NoError(conn.ExpectationsWereMet())
180 })
181
182 t.Run("failed transaction begin", func(*testing.T) {
183 conn.ExpectBegin().WillReturnError(errors.New("failed transaction begin"))
184
185 err = pgrw.WriteSources(mds)
186 a.Error(err)
187 a.NoError(conn.ExpectationsWereMet())
188 })
189
190 t.Run("failed truncate", func(*testing.T) {
191 conn.ExpectBegin()
192 conn.ExpectExec(`truncate pgwatch\.source`).WillReturnError(errors.New("failed truncate"))
193
194 err = pgrw.WriteSources(mds)
195 a.Error(err)
196 a.NoError(conn.ExpectationsWereMet())
197 })
198
199 t.Run("failed insert", func(*testing.T) {
200 conn.ExpectBegin()
201 conn.ExpectExec(`truncate pgwatch\.source`).WillReturnResult(pgxmock.NewResult("TRUNCATE", 1))
202 conn.ExpectExec(`insert into pgwatch\.source`).WithArgs(
203 md.Name, md.Group, md.Kind,
204 md.ConnStr, `{"metric":60}`, `{"standby_metric":60}`, md.PresetMetrics, md.PresetMetricsStandby,
205 md.IncludePattern, md.ExcludePattern, `{"tag":"value"}`,
206 nil, md.OnlyIfMaster,
207 ).WillReturnError(errors.New("failed insert"))
208 conn.ExpectRollback()
209
210 err = pgrw.WriteSources(mds)
211 a.Error(err)
212 a.NoError(conn.ExpectationsWereMet())
213 })
214 }
215