* Add lint/format checks to the Gitea release workflow and Makefile (targets: vet, fmt, fmt-check, staticcheck, govulncheck, check) * Switch .golangci.json formatter from gofmt to gofumpt (extra.group-params) * Bump golang.org/x/text 0.37.0 -> 0.39.0 for GO-2026-5970; re-vendor * Fix staticcheck S1011 in pkg/diff; drop unused pgsql writer helpers * Fix gocritic unnamedResult (pkg/diff) and rangeValCopy (pkg/pgsql) * Apply gofumpt + goimports formatting across the tree
275 lines
6.9 KiB
Go
275 lines
6.9 KiB
Go
package inspector
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestNewInspector(t *testing.T) {
|
|
db := createTestDatabase()
|
|
config := GetDefaultConfig()
|
|
|
|
inspector := NewInspector(db, config)
|
|
|
|
if inspector == nil {
|
|
t.Fatal("NewInspector() returned nil")
|
|
}
|
|
|
|
if inspector.db != db {
|
|
t.Error("NewInspector() database not set correctly")
|
|
}
|
|
|
|
if inspector.config != config {
|
|
t.Error("NewInspector() config not set correctly")
|
|
}
|
|
}
|
|
|
|
func TestInspect(t *testing.T) {
|
|
db := createTestDatabase()
|
|
config := GetDefaultConfig()
|
|
|
|
inspector := NewInspector(db, config)
|
|
report, err := inspector.Inspect()
|
|
if err != nil {
|
|
t.Fatalf("Inspect() returned error: %v", err)
|
|
}
|
|
|
|
if report == nil {
|
|
t.Fatal("Inspect() returned nil report")
|
|
}
|
|
|
|
if report.Database != db.Name {
|
|
t.Errorf("Inspect() report.Database = %q, want %q", report.Database, db.Name)
|
|
}
|
|
|
|
if report.Summary.TotalRules != len(config.Rules) {
|
|
t.Errorf("Inspect() TotalRules = %d, want %d", report.Summary.TotalRules, len(config.Rules))
|
|
}
|
|
|
|
if len(report.Violations) == 0 {
|
|
t.Error("Inspect() returned no violations, expected some results")
|
|
}
|
|
}
|
|
|
|
// TestInspect_Deterministic verifies that repeated Inspect() calls against
|
|
// the same database and config produce violations in the same order, instead
|
|
// of following Go's randomized map iteration order over config.Rules and the
|
|
// per-table Columns/Constraints/Indexes maps.
|
|
func TestInspect_Deterministic(t *testing.T) {
|
|
db := createTestDatabase()
|
|
config := GetDefaultConfig()
|
|
|
|
inspector := NewInspector(db, config)
|
|
|
|
first, err := inspector.Inspect()
|
|
if err != nil {
|
|
t.Fatalf("Inspect() returned error: %v", err)
|
|
}
|
|
|
|
wantOrder := make([]string, len(first.Violations))
|
|
for i, v := range first.Violations {
|
|
wantOrder[i] = v.RuleName + "|" + v.Location
|
|
}
|
|
|
|
for i := 0; i < 25; i++ {
|
|
report, err := inspector.Inspect()
|
|
if err != nil {
|
|
t.Fatalf("Inspect() returned error on run %d: %v", i, err)
|
|
}
|
|
|
|
if len(report.Violations) != len(wantOrder) {
|
|
t.Fatalf("run %d: got %d violations, want %d", i, len(report.Violations), len(wantOrder))
|
|
}
|
|
|
|
for j, v := range report.Violations {
|
|
got := v.RuleName + "|" + v.Location
|
|
if got != wantOrder[j] {
|
|
t.Fatalf("run %d: violation[%d] = %q, want %q", i, j, got, wantOrder[j])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInspectWithDisabledRules(t *testing.T) {
|
|
db := createTestDatabase()
|
|
config := GetDefaultConfig()
|
|
|
|
// Disable all rules
|
|
for name := range config.Rules {
|
|
rule := config.Rules[name]
|
|
rule.Enabled = "off"
|
|
config.Rules[name] = rule
|
|
}
|
|
|
|
inspector := NewInspector(db, config)
|
|
report, err := inspector.Inspect()
|
|
if err != nil {
|
|
t.Fatalf("Inspect() with disabled rules returned error: %v", err)
|
|
}
|
|
|
|
if report.Summary.RulesChecked != 0 {
|
|
t.Errorf("Inspect() RulesChecked = %d, want 0 (all disabled)", report.Summary.RulesChecked)
|
|
}
|
|
|
|
if report.Summary.RulesSkipped != len(config.Rules) {
|
|
t.Errorf("Inspect() RulesSkipped = %d, want %d", report.Summary.RulesSkipped, len(config.Rules))
|
|
}
|
|
}
|
|
|
|
func TestInspectWithEnforcedRules(t *testing.T) {
|
|
db := createTestDatabase()
|
|
config := GetDefaultConfig()
|
|
|
|
// Enable only one rule and enforce it
|
|
for name := range config.Rules {
|
|
rule := config.Rules[name]
|
|
rule.Enabled = "off"
|
|
config.Rules[name] = rule
|
|
}
|
|
|
|
primaryKeyRule := config.Rules["primary_key_naming"]
|
|
primaryKeyRule.Enabled = "enforce"
|
|
primaryKeyRule.Pattern = "^id$"
|
|
config.Rules["primary_key_naming"] = primaryKeyRule
|
|
|
|
inspector := NewInspector(db, config)
|
|
report, err := inspector.Inspect()
|
|
if err != nil {
|
|
t.Fatalf("Inspect() returned error: %v", err)
|
|
}
|
|
|
|
if report.Summary.RulesChecked != 1 {
|
|
t.Errorf("Inspect() RulesChecked = %d, want 1", report.Summary.RulesChecked)
|
|
}
|
|
|
|
// All results should be at error level for enforced rules
|
|
for _, violation := range report.Violations {
|
|
if violation.Level != "error" {
|
|
t.Errorf("Enforced rule violation has Level = %q, want \"error\"", violation.Level)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerateSummary(t *testing.T) {
|
|
db := createTestDatabase()
|
|
config := GetDefaultConfig()
|
|
inspector := NewInspector(db, config)
|
|
|
|
results := []ValidationResult{
|
|
{RuleName: "rule1", Passed: true, Level: "error"},
|
|
{RuleName: "rule2", Passed: false, Level: "error"},
|
|
{RuleName: "rule3", Passed: false, Level: "warning"},
|
|
{RuleName: "rule4", Passed: true, Level: "warning"},
|
|
}
|
|
|
|
summary := inspector.generateSummary(results)
|
|
|
|
if summary.PassedCount != 2 {
|
|
t.Errorf("generateSummary() PassedCount = %d, want 2", summary.PassedCount)
|
|
}
|
|
|
|
if summary.ErrorCount != 1 {
|
|
t.Errorf("generateSummary() ErrorCount = %d, want 1", summary.ErrorCount)
|
|
}
|
|
|
|
if summary.WarningCount != 1 {
|
|
t.Errorf("generateSummary() WarningCount = %d, want 1", summary.WarningCount)
|
|
}
|
|
}
|
|
|
|
func TestHasErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
report *InspectorReport
|
|
want bool
|
|
}{
|
|
{
|
|
name: "with errors",
|
|
report: &InspectorReport{
|
|
Summary: ReportSummary{
|
|
ErrorCount: 5,
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "without errors",
|
|
report: &InspectorReport{
|
|
Summary: ReportSummary{
|
|
ErrorCount: 0,
|
|
WarningCount: 3,
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := tt.report.HasErrors(); got != tt.want {
|
|
t.Errorf("HasErrors() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetValidator(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
functionName string
|
|
wantExists bool
|
|
}{
|
|
{"primary_key_naming", "primary_key_naming", true},
|
|
{"primary_key_datatype", "primary_key_datatype", true},
|
|
{"foreign_key_column_naming", "foreign_key_column_naming", true},
|
|
{"table_regexpr", "table_regexpr", true},
|
|
{"column_regexpr", "column_regexpr", true},
|
|
{"reserved_words", "reserved_words", true},
|
|
{"have_primary_key", "have_primary_key", true},
|
|
{"orphaned_foreign_key", "orphaned_foreign_key", true},
|
|
{"circular_dependency", "circular_dependency", true},
|
|
{"unknown_function", "unknown_function", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, exists := getValidator(tt.functionName)
|
|
if exists != tt.wantExists {
|
|
t.Errorf("getValidator(%q) exists = %v, want %v", tt.functionName, exists, tt.wantExists)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateResult(t *testing.T) {
|
|
result := createResult(
|
|
"test_rule",
|
|
true,
|
|
"Test message",
|
|
"schema.table.column",
|
|
map[string]interface{}{
|
|
"key1": "value1",
|
|
"key2": 42,
|
|
},
|
|
)
|
|
|
|
if result.RuleName != "test_rule" {
|
|
t.Errorf("createResult() RuleName = %q, want \"test_rule\"", result.RuleName)
|
|
}
|
|
|
|
if !result.Passed {
|
|
t.Error("createResult() Passed = false, want true")
|
|
}
|
|
|
|
if result.Message != "Test message" {
|
|
t.Errorf("createResult() Message = %q, want \"Test message\"", result.Message)
|
|
}
|
|
|
|
if result.Location != "schema.table.column" {
|
|
t.Errorf("createResult() Location = %q, want \"schema.table.column\"", result.Location)
|
|
}
|
|
|
|
if len(result.Context) != 2 {
|
|
t.Errorf("createResult() Context length = %d, want 2", len(result.Context))
|
|
}
|
|
}
|