test(pgsql, reflectutil): ✨ add comprehensive test coverage
All checks were successful
All checks were successful
* Introduce tests for PostgreSQL data types and keywords. * Implement tests for reflect utility functions. * Ensure consistency and correctness of type conversions and keyword mappings. * Validate behavior for various edge cases and input types.
This commit is contained in:
366
pkg/inspector/report_test.go
Normal file
366
pkg/inspector/report_test.go
Normal file
@@ -0,0 +1,366 @@
|
||||
package inspector
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func createTestReport() *InspectorReport {
|
||||
return &InspectorReport{
|
||||
Summary: ReportSummary{
|
||||
TotalRules: 10,
|
||||
RulesChecked: 8,
|
||||
RulesSkipped: 2,
|
||||
ErrorCount: 3,
|
||||
WarningCount: 5,
|
||||
PassedCount: 12,
|
||||
},
|
||||
Violations: []ValidationResult{
|
||||
{
|
||||
RuleName: "primary_key_naming",
|
||||
Level: "error",
|
||||
Message: "Primary key should start with 'id_'",
|
||||
Location: "public.users.user_id",
|
||||
Passed: false,
|
||||
Context: map[string]interface{}{
|
||||
"schema": "public",
|
||||
"table": "users",
|
||||
"column": "user_id",
|
||||
"pattern": "^id_",
|
||||
},
|
||||
},
|
||||
{
|
||||
RuleName: "table_name_length",
|
||||
Level: "warning",
|
||||
Message: "Table name too long",
|
||||
Location: "public.very_long_table_name_that_exceeds_limits",
|
||||
Passed: false,
|
||||
Context: map[string]interface{}{
|
||||
"schema": "public",
|
||||
"table": "very_long_table_name_that_exceeds_limits",
|
||||
"length": 44,
|
||||
"max_length": 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
GeneratedAt: time.Now(),
|
||||
Database: "testdb",
|
||||
SourceFormat: "postgresql",
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMarkdownFormatter(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
formatter := NewMarkdownFormatter(&buf)
|
||||
|
||||
if formatter == nil {
|
||||
t.Fatal("NewMarkdownFormatter() returned nil")
|
||||
}
|
||||
|
||||
// Buffer is not a terminal, so colors should be disabled
|
||||
if formatter.UseColors {
|
||||
t.Error("NewMarkdownFormatter() UseColors should be false for non-terminal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewJSONFormatter(t *testing.T) {
|
||||
formatter := NewJSONFormatter()
|
||||
|
||||
if formatter == nil {
|
||||
t.Fatal("NewJSONFormatter() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkdownFormatter_Format(t *testing.T) {
|
||||
report := createTestReport()
|
||||
var buf bytes.Buffer
|
||||
formatter := NewMarkdownFormatter(&buf)
|
||||
|
||||
output, err := formatter.Format(report)
|
||||
if err != nil {
|
||||
t.Fatalf("MarkdownFormatter.Format() returned error: %v", err)
|
||||
}
|
||||
|
||||
// Check that output contains expected sections
|
||||
if !strings.Contains(output, "# RelSpec Inspector Report") {
|
||||
t.Error("Markdown output missing header")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "Database:") {
|
||||
t.Error("Markdown output missing database field")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "testdb") {
|
||||
t.Error("Markdown output missing database name")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "Summary") {
|
||||
t.Error("Markdown output missing summary section")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "Rules Checked: 8") {
|
||||
t.Error("Markdown output missing rules checked count")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "Errors: 3") {
|
||||
t.Error("Markdown output missing error count")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "Warnings: 5") {
|
||||
t.Error("Markdown output missing warning count")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "Violations") {
|
||||
t.Error("Markdown output missing violations section")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "primary_key_naming") {
|
||||
t.Error("Markdown output missing rule name")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "public.users.user_id") {
|
||||
t.Error("Markdown output missing location")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkdownFormatter_FormatNoViolations(t *testing.T) {
|
||||
report := &InspectorReport{
|
||||
Summary: ReportSummary{
|
||||
TotalRules: 10,
|
||||
RulesChecked: 10,
|
||||
RulesSkipped: 0,
|
||||
ErrorCount: 0,
|
||||
WarningCount: 0,
|
||||
PassedCount: 50,
|
||||
},
|
||||
Violations: []ValidationResult{},
|
||||
GeneratedAt: time.Now(),
|
||||
Database: "testdb",
|
||||
SourceFormat: "postgresql",
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
formatter := NewMarkdownFormatter(&buf)
|
||||
|
||||
output, err := formatter.Format(report)
|
||||
if err != nil {
|
||||
t.Fatalf("MarkdownFormatter.Format() returned error: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "No violations found") {
|
||||
t.Error("Markdown output should indicate no violations")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONFormatter_Format(t *testing.T) {
|
||||
report := createTestReport()
|
||||
formatter := NewJSONFormatter()
|
||||
|
||||
output, err := formatter.Format(report)
|
||||
if err != nil {
|
||||
t.Fatalf("JSONFormatter.Format() returned error: %v", err)
|
||||
}
|
||||
|
||||
// Verify it's valid JSON
|
||||
var decoded InspectorReport
|
||||
if err := json.Unmarshal([]byte(output), &decoded); err != nil {
|
||||
t.Fatalf("JSONFormatter.Format() produced invalid JSON: %v", err)
|
||||
}
|
||||
|
||||
// Check key fields
|
||||
if decoded.Database != "testdb" {
|
||||
t.Errorf("JSON decoded Database = %q, want \"testdb\"", decoded.Database)
|
||||
}
|
||||
|
||||
if decoded.Summary.ErrorCount != 3 {
|
||||
t.Errorf("JSON decoded ErrorCount = %d, want 3", decoded.Summary.ErrorCount)
|
||||
}
|
||||
|
||||
if len(decoded.Violations) != 2 {
|
||||
t.Errorf("JSON decoded Violations length = %d, want 2", len(decoded.Violations))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkdownFormatter_FormatHeader(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
formatter := NewMarkdownFormatter(&buf)
|
||||
|
||||
header := formatter.formatHeader("Test Header")
|
||||
|
||||
if !strings.Contains(header, "# Test Header") {
|
||||
t.Errorf("formatHeader() = %q, want to contain \"# Test Header\"", header)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkdownFormatter_FormatBold(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
useColors bool
|
||||
text string
|
||||
wantContains string
|
||||
}{
|
||||
{
|
||||
name: "without colors",
|
||||
useColors: false,
|
||||
text: "Bold Text",
|
||||
wantContains: "**Bold Text**",
|
||||
},
|
||||
{
|
||||
name: "with colors",
|
||||
useColors: true,
|
||||
text: "Bold Text",
|
||||
wantContains: "Bold Text",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
formatter := &MarkdownFormatter{UseColors: tt.useColors}
|
||||
result := formatter.formatBold(tt.text)
|
||||
|
||||
if !strings.Contains(result, tt.wantContains) {
|
||||
t.Errorf("formatBold() = %q, want to contain %q", result, tt.wantContains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkdownFormatter_Colorize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
useColors bool
|
||||
text string
|
||||
color string
|
||||
wantColor bool
|
||||
}{
|
||||
{
|
||||
name: "without colors",
|
||||
useColors: false,
|
||||
text: "Test",
|
||||
color: colorRed,
|
||||
wantColor: false,
|
||||
},
|
||||
{
|
||||
name: "with colors",
|
||||
useColors: true,
|
||||
text: "Test",
|
||||
color: colorRed,
|
||||
wantColor: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
formatter := &MarkdownFormatter{UseColors: tt.useColors}
|
||||
result := formatter.colorize(tt.text, tt.color)
|
||||
|
||||
hasColor := strings.Contains(result, tt.color)
|
||||
if hasColor != tt.wantColor {
|
||||
t.Errorf("colorize() has color codes = %v, want %v", hasColor, tt.wantColor)
|
||||
}
|
||||
|
||||
if !strings.Contains(result, tt.text) {
|
||||
t.Errorf("colorize() doesn't contain original text %q", tt.text)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkdownFormatter_FormatContext(t *testing.T) {
|
||||
formatter := &MarkdownFormatter{UseColors: false}
|
||||
|
||||
context := map[string]interface{}{
|
||||
"schema": "public",
|
||||
"table": "users",
|
||||
"column": "id",
|
||||
"pattern": "^id_",
|
||||
"max_length": 64,
|
||||
}
|
||||
|
||||
result := formatter.formatContext(context)
|
||||
|
||||
// Should not include schema, table, column (they're in location)
|
||||
if strings.Contains(result, "schema") {
|
||||
t.Error("formatContext() should skip schema field")
|
||||
}
|
||||
|
||||
if strings.Contains(result, "table=") {
|
||||
t.Error("formatContext() should skip table field")
|
||||
}
|
||||
|
||||
if strings.Contains(result, "column=") {
|
||||
t.Error("formatContext() should skip column field")
|
||||
}
|
||||
|
||||
// Should include other fields
|
||||
if !strings.Contains(result, "pattern") {
|
||||
t.Error("formatContext() should include pattern field")
|
||||
}
|
||||
|
||||
if !strings.Contains(result, "max_length") {
|
||||
t.Error("formatContext() should include max_length field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkdownFormatter_FormatViolation(t *testing.T) {
|
||||
formatter := &MarkdownFormatter{UseColors: false}
|
||||
|
||||
violation := ValidationResult{
|
||||
RuleName: "test_rule",
|
||||
Level: "error",
|
||||
Message: "Test violation message",
|
||||
Location: "public.users.id",
|
||||
Passed: false,
|
||||
Context: map[string]interface{}{
|
||||
"pattern": "^id_",
|
||||
},
|
||||
}
|
||||
|
||||
result := formatter.formatViolation(violation, colorRed)
|
||||
|
||||
if !strings.Contains(result, "test_rule") {
|
||||
t.Error("formatViolation() should include rule name")
|
||||
}
|
||||
|
||||
if !strings.Contains(result, "Test violation message") {
|
||||
t.Error("formatViolation() should include message")
|
||||
}
|
||||
|
||||
if !strings.Contains(result, "public.users.id") {
|
||||
t.Error("formatViolation() should include location")
|
||||
}
|
||||
|
||||
if !strings.Contains(result, "Location:") {
|
||||
t.Error("formatViolation() should include Location label")
|
||||
}
|
||||
|
||||
if !strings.Contains(result, "Message:") {
|
||||
t.Error("formatViolation() should include Message label")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReportFormatConstants(t *testing.T) {
|
||||
// Test that color constants are defined
|
||||
if colorReset == "" {
|
||||
t.Error("colorReset is not defined")
|
||||
}
|
||||
|
||||
if colorRed == "" {
|
||||
t.Error("colorRed is not defined")
|
||||
}
|
||||
|
||||
if colorYellow == "" {
|
||||
t.Error("colorYellow is not defined")
|
||||
}
|
||||
|
||||
if colorGreen == "" {
|
||||
t.Error("colorGreen is not defined")
|
||||
}
|
||||
|
||||
if colorBold == "" {
|
||||
t.Error("colorBold is not defined")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user