-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathpostgres_test.go
More file actions
197 lines (177 loc) · 7.16 KB
/
postgres_test.go
File metadata and controls
197 lines (177 loc) · 7.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/*-------------------------------------------------------------------------
*
* radar
*
* Portions copyright (c) 2026, pgEdge, Inc.
* This software is released under The PostgreSQL License
*
*-------------------------------------------------------------------------
*/
package main
import (
"bytes"
"errors"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jackc/pgx/v5/pgconn"
)
// TestPostgreSQLCollectors verifies all expected PostgreSQL collectors are registered
// with correct metadata using a table-driven approach
func TestPostgreSQLCollectors(t *testing.T) {
// Expected collectors with their metadata
expected := []struct {
name string
archivePath string
}{
// Instance-level collectors (alphabetically ordered)
{"activity", "postgresql/running_activity.tsv"},
{"archiver", "postgresql/archiver.tsv"},
{"available_extensions", "postgresql/available_extensions.tsv"},
{"bgwriter", "postgresql/bgwriter.tsv"},
{"blocking_locks", "postgresql/blocking_locks.tsv"},
{"checkpointer", "postgresql/checkpointer.tsv"},
{"configuration", "postgresql/configuration.tsv"},
{"connection_summary", "postgresql/connection_summary.tsv"},
{"database_conflicts", "postgresql/database_conflicts.tsv"},
{"database_sizes", "postgresql/database_sizes.tsv"},
{"databases", "postgresql/databases.tsv"},
{"databases_blk", "postgresql/databases_blk.tsv"},
{"databases_checksums", "postgresql/databases_checksums.tsv"},
{"databases_tup", "postgresql/databases_tup.tsv"},
{"databases_xact", "postgresql/databases_xact.tsv"},
{"db_role_setting", "postgresql/db_role_setting.tsv"},
{"file_settings", "postgresql/file_settings.tsv"},
{"pg_hba.conf", "postgresql/pg_hba.conf"},
{"pg_hba_file_rules", "postgresql/pg_hba_file_rules.tsv"},
{"pg_ident.conf", "postgresql/pg_ident.conf"},
{"postmaster_start_time", "postgresql/postmaster_start_time.tsv"},
{"postgresql.auto.conf", "postgresql/postgresql.auto.conf"},
{"postgresql.conf", "postgresql/postgresql.conf"},
{"prepared_xacts", "postgresql/prepared_xacts.tsv"},
{"recovery.conf", "postgresql/recovery.conf"},
{"recovery.done", "postgresql/recovery.done"},
{"replication", "postgresql/replication.tsv"},
{"replication_origin", "postgresql/replication_origin.tsv"},
{"replication_slots", "postgresql/replication_slots.tsv"},
{"roles", "postgresql/roles.tsv"},
{"running_activity_maxage", "postgresql/running_activity_maxage.tsv"},
{"running_locks", "postgresql/running_locks.tsv"},
{"shmem_allocations", "postgresql/shmem_allocations.tsv"},
{"stat_io", "postgresql/stat_io.tsv"},
{"stat_progress_analyze", "postgresql/stat_progress_analyze.tsv"},
{"stat_progress_basebackup", "postgresql/stat_progress_basebackup.tsv"},
{"stat_progress_cluster", "postgresql/stat_progress_cluster.tsv"},
{"stat_progress_copy", "postgresql/stat_progress_copy.tsv"},
{"stat_progress_create_index", "postgresql/stat_progress_create_index.tsv"},
{"stat_progress_vacuum", "postgresql/stat_progress_vacuum.tsv"},
{"stat_slru", "postgresql/stat_slru.tsv"},
{"stat_statements_calls", "postgresql/stat_statements_calls.tsv"},
{"stat_statements_max_time", "postgresql/stat_statements_max_time.tsv"},
{"stat_statements_total_time", "postgresql/stat_statements_total_time.tsv"},
{"stat_wal", "postgresql/stat_wal.tsv"},
{"subscriptions", "postgresql/subscriptions.tsv"},
{"tablespace_sizes", "postgresql/tablespace_sizes.tsv"},
{"tablespaces", "postgresql/tablespaces.tsv"},
{"version", "postgresql/version.tsv"},
{"waits_sample", "postgresql/waits_sample.tsv"},
{"wal_position", "postgresql/wal_position.tsv"},
{"wal_receiver", "postgresql/wal_receiver.tsv"},
}
tasks := getPostgreSQLTasks(nil)
if len(tasks) == 0 {
t.Fatal("getPostgreSQLTasks returned no tasks")
}
// Build a map for easy lookup
taskMap := make(map[string]*CollectionTask)
for i := range tasks {
taskMap[tasks[i].Name] = &tasks[i]
}
// Verify each expected collector exists with correct metadata
for _, exp := range expected {
t.Run(exp.name, func(t *testing.T) {
task, found := taskMap[exp.name]
if !found {
t.Fatalf("collector %q not found in getPostgreSQLTasks()", exp.name)
}
if task.Category != "postgresql" {
t.Errorf("expected category 'postgresql', got %q", task.Category)
}
if task.ArchivePath != exp.archivePath {
t.Errorf("expected archive path %q, got %q", exp.archivePath, task.ArchivePath)
}
if task.Collector == nil {
t.Fatal("collector function is nil")
}
})
}
// Verify we have the expected number of collectors
if len(tasks) != len(expected) {
t.Errorf("expected %d collectors, got %d", len(expected), len(tasks))
}
}
// TestPostgreSQLTasksStructure verifies all PostgreSQL tasks have required fields
func TestPostgreSQLTasksStructure(t *testing.T) {
tasks := getPostgreSQLTasks(nil)
if len(tasks) == 0 {
t.Fatal("getPostgreSQLTasks returned no tasks")
}
for i, task := range tasks {
if task.Category == "" {
t.Errorf("task %d missing Category", i)
}
if task.Name == "" {
t.Errorf("task %d missing Name", i)
}
if task.ArchivePath == "" {
t.Errorf("task %d missing ArchivePath", i)
}
if task.Collector == nil {
t.Errorf("task %d missing Collector function", i)
}
// Verify all PostgreSQL tasks have category "postgresql"
if task.Category != "postgresql" {
t.Errorf("task %d (%s) has category %q, expected \"postgresql\"", i, task.Name, task.Category)
}
// Verify archive paths don't start with slash
if strings.HasPrefix(task.ArchivePath, "/") {
t.Errorf("task %d (%s) ArchivePath starts with /: %s", i, task.Name, task.ArchivePath)
}
}
}
// TestPGQueryCollectorUnavailableAsSkip verifies that PG errors for missing
// extensions (undefined_table/function/object, invalid_schema) are returned
// as SkipError, while real errors (permission denied) are returned as-is.
func TestPGQueryCollectorUnavailableAsSkip(t *testing.T) {
tests := []struct {
name string
pgErr *pgconn.PgError
wantSkip bool
}{
{"undefined_table is skip", &pgconn.PgError{Code: "42P01", Message: "relation \"pg_stat_statements\" does not exist"}, true},
{"undefined_function is skip", &pgconn.PgError{Code: "42883", Message: "function does not exist"}, true},
{"undefined_object is skip", &pgconn.PgError{Code: "42704", Message: "type does not exist"}, true},
{"invalid_schema is skip", &pgconn.PgError{Code: "3F000", Message: "schema \"pgstatviz\" does not exist"}, true},
{"permission_denied is real error", &pgconn.PgError{Code: "42501", Message: "permission denied"}, false},
{"syntax_error is real error", &pgconn.PgError{Code: "42601", Message: "syntax error"}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create mock: %v", err)
}
mock.ExpectQuery("SELECT").WillReturnError(tt.pgErr)
collector := pgQueryCollector(db, "SELECT 1")
err = collector(&Config{}, &bytes.Buffer{})
if err == nil {
t.Fatal("expected error, got nil")
}
var skipErr SkipError
isSkip := errors.As(err, &skipErr)
if isSkip != tt.wantSkip {
t.Errorf("errors.As SkipError = %v, want %v (err: %v)", isSkip, tt.wantSkip, err)
}
})
}
}