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 TestDeleteDatabase(t *testing.T) {
70 a := assert.New(t)
71 conn, err := pgxmock.NewPool()
72 a.NoError(err)
73 conn.ExpectPing()
74 conn.ExpectExec(`delete from pgwatch\.source where name = \$1`).WithArgs("db1").WillReturnResult(pgxmock.NewResult("DELETE", 1))
75 pgrw, err := sources.NewPostgresSourcesReaderWriterConn(ctx, conn)
76 a.NoError(err)
77
78 err = pgrw.DeleteSource("db1")
79 a.NoError(err)
80 a.NoError(conn.ExpectationsWereMet())
81 }
82
83 func TestUpdateDatabase(t *testing.T) {
84 a := assert.New(t)
85 conn, err := pgxmock.NewPool()
86 a.NoError(err)
87
88 md := sources.Source{
89 Name: "db1",
90 Group: "group1",
91 Kind: sources.Kind("postgres"),
92 ConnStr: "postgres://user:pass@localhost:5432/db1",
93 Metrics: map[string]float64{"metric": 60},
94 MetricsStandby: map[string]float64{"standby_metric": 60},
95 IncludePattern: ".*",
96 ExcludePattern: `\_.+`,
97 CustomTags: map[string]string{"tag": "value"},
98 }
99 conn.ExpectPing()
100 conn.ExpectExec(`insert into pgwatch\.source`).WithArgs(
101 md.Name, md.Group, md.Kind,
102 md.ConnStr, `{"metric":60}`, `{"standby_metric":60}`,
103 md.PresetMetrics, md.PresetMetricsStandby,
104 md.IncludePattern, md.ExcludePattern, `{"tag":"value"}`,
105 nil, md.OnlyIfMaster, md.IsEnabled,
106 ).WillReturnResult(pgxmock.NewResult("UPDATE", 1))
107
108 pgrw, err := sources.NewPostgresSourcesReaderWriterConn(ctx, conn)
109 a.NoError(err)
110 err = pgrw.UpdateSource(md)
111 a.NoError(err)
112 a.NoError(conn.ExpectationsWereMet())
113 }
114
115 func TestWriteMonitoredDatabases(t *testing.T) {
116 var (
117 pgrw sources.ReaderWriter
118 err error
119 )
120 a := assert.New(t)
121 conn, err := pgxmock.NewPool()
122 a.NoError(err)
123 md := sources.Source{
124 Name: "db1",
125 Group: "group1",
126 Kind: sources.Kind("postgres"),
127 ConnStr: "postgres://user:pass@localhost:5432/db1",
128 Metrics: map[string]float64{"metric": 60},
129 MetricsStandby: map[string]float64{"standby_metric": 60},
130 IncludePattern: ".*",
131 ExcludePattern: `\_.+`,
132 CustomTags: map[string]string{"tag": "value"},
133 }
134 mds := sources.Sources{md}
135
136 t.Run("happy path", func(*testing.T) {
137 conn.ExpectPing()
138 conn.ExpectBegin()
139 conn.ExpectExec(`truncate pgwatch\.source`).WillReturnResult(pgxmock.NewResult("TRUNCATE", 1))
140 conn.ExpectExec(`insert into pgwatch\.source`).WithArgs(
141 md.Name, md.Group, md.Kind,
142 md.ConnStr, `{"metric":60}`, `{"standby_metric":60}`, md.PresetMetrics, md.PresetMetricsStandby,
143 md.IncludePattern, md.ExcludePattern, `{"tag":"value"}`,
144 nil, md.OnlyIfMaster, md.IsEnabled,
145 ).WillReturnResult(pgxmock.NewResult("INSERT", 1))
146 conn.ExpectCommit()
147 conn.ExpectRollback()
148
149 pgrw, err = sources.NewPostgresSourcesReaderWriterConn(ctx, conn)
150 a.NoError(err)
151 err = pgrw.WriteSources(mds)
152 a.NoError(err)
153 a.NoError(conn.ExpectationsWereMet())
154 })
155
156 t.Run("failed transaction begin", func(*testing.T) {
157 conn.ExpectBegin().WillReturnError(errors.New("failed transaction begin"))
158
159 err = pgrw.WriteSources(mds)
160 a.Error(err)
161 a.NoError(conn.ExpectationsWereMet())
162 })
163
164 t.Run("failed truncate", func(*testing.T) {
165 conn.ExpectBegin()
166 conn.ExpectExec(`truncate pgwatch\.source`).WillReturnError(errors.New("failed truncate"))
167
168 err = pgrw.WriteSources(mds)
169 a.Error(err)
170 a.NoError(conn.ExpectationsWereMet())
171 })
172
173 t.Run("failed insert", func(*testing.T) {
174 conn.ExpectBegin()
175 conn.ExpectExec(`truncate pgwatch\.source`).WillReturnResult(pgxmock.NewResult("TRUNCATE", 1))
176 conn.ExpectExec(`insert into pgwatch\.source`).WithArgs(
177 md.Name, md.Group, md.Kind,
178 md.ConnStr, `{"metric":60}`, `{"standby_metric":60}`, md.PresetMetrics, md.PresetMetricsStandby,
179 md.IncludePattern, md.ExcludePattern, `{"tag":"value"}`,
180 nil, md.OnlyIfMaster, md.IsEnabled,
181 ).WillReturnError(errors.New("failed insert"))
182 conn.ExpectRollback()
183
184 err = pgrw.WriteSources(mds)
185 a.Error(err)
186 a.NoError(conn.ExpectationsWereMet())
187 })
188 }
189