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:
837
pkg/inspector/validators_test.go
Normal file
837
pkg/inspector/validators_test.go
Normal file
@@ -0,0 +1,837 @@
|
||||
package inspector
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
)
|
||||
|
||||
// Helper function to create test database
|
||||
func createTestDatabase() *models.Database {
|
||||
return &models.Database{
|
||||
Name: "testdb",
|
||||
Schemas: []*models.Schema{
|
||||
{
|
||||
Name: "public",
|
||||
Tables: []*models.Table{
|
||||
{
|
||||
Name: "users",
|
||||
Columns: map[string]*models.Column{
|
||||
"id": {
|
||||
Name: "id",
|
||||
Type: "bigserial",
|
||||
IsPrimaryKey: true,
|
||||
AutoIncrement: true,
|
||||
},
|
||||
"username": {
|
||||
Name: "username",
|
||||
Type: "varchar(50)",
|
||||
NotNull: true,
|
||||
IsPrimaryKey: false,
|
||||
},
|
||||
"rid_organization": {
|
||||
Name: "rid_organization",
|
||||
Type: "bigint",
|
||||
NotNull: true,
|
||||
IsPrimaryKey: false,
|
||||
},
|
||||
},
|
||||
Constraints: map[string]*models.Constraint{
|
||||
"fk_users_organization": {
|
||||
Name: "fk_users_organization",
|
||||
Type: models.ForeignKeyConstraint,
|
||||
Columns: []string{"rid_organization"},
|
||||
ReferencedTable: "organizations",
|
||||
ReferencedSchema: "public",
|
||||
ReferencedColumns: []string{"id"},
|
||||
},
|
||||
},
|
||||
Indexes: map[string]*models.Index{
|
||||
"idx_rid_organization": {
|
||||
Name: "idx_rid_organization",
|
||||
Columns: []string{"rid_organization"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "organizations",
|
||||
Columns: map[string]*models.Column{
|
||||
"id": {
|
||||
Name: "id",
|
||||
Type: "bigserial",
|
||||
IsPrimaryKey: true,
|
||||
AutoIncrement: true,
|
||||
},
|
||||
"name": {
|
||||
Name: "name",
|
||||
Type: "varchar(100)",
|
||||
NotNull: true,
|
||||
IsPrimaryKey: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePrimaryKeyNaming(t *testing.T) {
|
||||
db := createTestDatabase()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
wantPass bool
|
||||
}{
|
||||
{
|
||||
name: "matching pattern id",
|
||||
rule: Rule{
|
||||
Pattern: "^id$",
|
||||
Message: "Primary key should be 'id'",
|
||||
},
|
||||
wantLen: 2,
|
||||
wantPass: true,
|
||||
},
|
||||
{
|
||||
name: "non-matching pattern id_",
|
||||
rule: Rule{
|
||||
Pattern: "^id_",
|
||||
Message: "Primary key should start with 'id_'",
|
||||
},
|
||||
wantLen: 2,
|
||||
wantPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validatePrimaryKeyNaming(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validatePrimaryKeyNaming() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
if len(results) > 0 && results[0].Passed != tt.wantPass {
|
||||
t.Errorf("validatePrimaryKeyNaming() passed=%v, want %v", results[0].Passed, tt.wantPass)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePrimaryKeyDatatype(t *testing.T) {
|
||||
db := createTestDatabase()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
wantPass bool
|
||||
}{
|
||||
{
|
||||
name: "allowed type bigserial",
|
||||
rule: Rule{
|
||||
AllowedTypes: []string{"bigserial", "bigint", "int"},
|
||||
Message: "Primary key should use integer types",
|
||||
},
|
||||
wantLen: 2,
|
||||
wantPass: true,
|
||||
},
|
||||
{
|
||||
name: "disallowed type",
|
||||
rule: Rule{
|
||||
AllowedTypes: []string{"uuid"},
|
||||
Message: "Primary key should use UUID",
|
||||
},
|
||||
wantLen: 2,
|
||||
wantPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validatePrimaryKeyDatatype(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validatePrimaryKeyDatatype() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
if len(results) > 0 && results[0].Passed != tt.wantPass {
|
||||
t.Errorf("validatePrimaryKeyDatatype() passed=%v, want %v", results[0].Passed, tt.wantPass)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePrimaryKeyAutoIncrement(t *testing.T) {
|
||||
db := createTestDatabase()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
name: "require auto increment",
|
||||
rule: Rule{
|
||||
RequireAutoIncrement: true,
|
||||
Message: "Primary key should have auto-increment",
|
||||
},
|
||||
wantLen: 0, // No violations - all PKs have auto-increment
|
||||
},
|
||||
{
|
||||
name: "disallow auto increment",
|
||||
rule: Rule{
|
||||
RequireAutoIncrement: false,
|
||||
Message: "Primary key should not have auto-increment",
|
||||
},
|
||||
wantLen: 2, // 2 violations - both PKs have auto-increment
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validatePrimaryKeyAutoIncrement(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validatePrimaryKeyAutoIncrement() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateForeignKeyColumnNaming(t *testing.T) {
|
||||
db := createTestDatabase()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
wantPass bool
|
||||
}{
|
||||
{
|
||||
name: "matching pattern rid_",
|
||||
rule: Rule{
|
||||
Pattern: "^rid_",
|
||||
Message: "Foreign key columns should start with 'rid_'",
|
||||
},
|
||||
wantLen: 1,
|
||||
wantPass: true,
|
||||
},
|
||||
{
|
||||
name: "non-matching pattern fk_",
|
||||
rule: Rule{
|
||||
Pattern: "^fk_",
|
||||
Message: "Foreign key columns should start with 'fk_'",
|
||||
},
|
||||
wantLen: 1,
|
||||
wantPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validateForeignKeyColumnNaming(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validateForeignKeyColumnNaming() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
if len(results) > 0 && results[0].Passed != tt.wantPass {
|
||||
t.Errorf("validateForeignKeyColumnNaming() passed=%v, want %v", results[0].Passed, tt.wantPass)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateForeignKeyConstraintNaming(t *testing.T) {
|
||||
db := createTestDatabase()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
wantPass bool
|
||||
}{
|
||||
{
|
||||
name: "matching pattern fk_",
|
||||
rule: Rule{
|
||||
Pattern: "^fk_",
|
||||
Message: "Foreign key constraints should start with 'fk_'",
|
||||
},
|
||||
wantLen: 1,
|
||||
wantPass: true,
|
||||
},
|
||||
{
|
||||
name: "non-matching pattern FK_",
|
||||
rule: Rule{
|
||||
Pattern: "^FK_",
|
||||
Message: "Foreign key constraints should start with 'FK_'",
|
||||
},
|
||||
wantLen: 1,
|
||||
wantPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validateForeignKeyConstraintNaming(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validateForeignKeyConstraintNaming() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
if len(results) > 0 && results[0].Passed != tt.wantPass {
|
||||
t.Errorf("validateForeignKeyConstraintNaming() passed=%v, want %v", results[0].Passed, tt.wantPass)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateForeignKeyIndex(t *testing.T) {
|
||||
db := createTestDatabase()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
wantPass bool
|
||||
}{
|
||||
{
|
||||
name: "require index with index present",
|
||||
rule: Rule{
|
||||
RequireIndex: true,
|
||||
Message: "Foreign key columns should have indexes",
|
||||
},
|
||||
wantLen: 1,
|
||||
wantPass: true,
|
||||
},
|
||||
{
|
||||
name: "no requirement",
|
||||
rule: Rule{
|
||||
RequireIndex: false,
|
||||
Message: "Foreign key index check disabled",
|
||||
},
|
||||
wantLen: 0,
|
||||
wantPass: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validateForeignKeyIndex(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validateForeignKeyIndex() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
if len(results) > 0 && results[0].Passed != tt.wantPass {
|
||||
t.Errorf("validateForeignKeyIndex() passed=%v, want %v", results[0].Passed, tt.wantPass)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTableNamingCase(t *testing.T) {
|
||||
db := createTestDatabase()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
wantPass bool
|
||||
}{
|
||||
{
|
||||
name: "lowercase snake_case pattern",
|
||||
rule: Rule{
|
||||
Pattern: "^[a-z][a-z0-9_]*$",
|
||||
Case: "lowercase",
|
||||
Message: "Table names should be lowercase snake_case",
|
||||
},
|
||||
wantLen: 2,
|
||||
wantPass: true,
|
||||
},
|
||||
{
|
||||
name: "uppercase pattern",
|
||||
rule: Rule{
|
||||
Pattern: "^[A-Z][A-Z0-9_]*$",
|
||||
Case: "uppercase",
|
||||
Message: "Table names should be uppercase",
|
||||
},
|
||||
wantLen: 2,
|
||||
wantPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validateTableNamingCase(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validateTableNamingCase() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
if len(results) > 0 && results[0].Passed != tt.wantPass {
|
||||
t.Errorf("validateTableNamingCase() passed=%v, want %v", results[0].Passed, tt.wantPass)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateColumnNamingCase(t *testing.T) {
|
||||
db := createTestDatabase()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
wantPass bool
|
||||
}{
|
||||
{
|
||||
name: "lowercase snake_case pattern",
|
||||
rule: Rule{
|
||||
Pattern: "^[a-z][a-z0-9_]*$",
|
||||
Case: "lowercase",
|
||||
Message: "Column names should be lowercase snake_case",
|
||||
},
|
||||
wantLen: 5, // 5 total columns across both tables
|
||||
wantPass: true,
|
||||
},
|
||||
{
|
||||
name: "camelCase pattern",
|
||||
rule: Rule{
|
||||
Pattern: "^[a-z][a-zA-Z0-9]*$",
|
||||
Case: "camelCase",
|
||||
Message: "Column names should be camelCase",
|
||||
},
|
||||
wantLen: 5,
|
||||
wantPass: false, // rid_organization has underscore
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validateColumnNamingCase(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validateColumnNamingCase() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTableNameLength(t *testing.T) {
|
||||
db := createTestDatabase()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
wantPass bool
|
||||
}{
|
||||
{
|
||||
name: "max length 64",
|
||||
rule: Rule{
|
||||
MaxLength: 64,
|
||||
Message: "Table name too long",
|
||||
},
|
||||
wantLen: 2,
|
||||
wantPass: true,
|
||||
},
|
||||
{
|
||||
name: "max length 5",
|
||||
rule: Rule{
|
||||
MaxLength: 5,
|
||||
Message: "Table name too long",
|
||||
},
|
||||
wantLen: 2,
|
||||
wantPass: false, // "users" is 5 chars (passes), "organizations" is 13 (fails)
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validateTableNameLength(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validateTableNameLength() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateColumnNameLength(t *testing.T) {
|
||||
db := createTestDatabase()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
wantPass bool
|
||||
}{
|
||||
{
|
||||
name: "max length 64",
|
||||
rule: Rule{
|
||||
MaxLength: 64,
|
||||
Message: "Column name too long",
|
||||
},
|
||||
wantLen: 5,
|
||||
wantPass: true,
|
||||
},
|
||||
{
|
||||
name: "max length 5",
|
||||
rule: Rule{
|
||||
MaxLength: 5,
|
||||
Message: "Column name too long",
|
||||
},
|
||||
wantLen: 5,
|
||||
wantPass: false, // Some columns exceed 5 chars
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validateColumnNameLength(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validateColumnNameLength() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateReservedKeywords(t *testing.T) {
|
||||
// Create a database with reserved keywords
|
||||
db := &models.Database{
|
||||
Name: "testdb",
|
||||
Schemas: []*models.Schema{
|
||||
{
|
||||
Name: "public",
|
||||
Tables: []*models.Table{
|
||||
{
|
||||
Name: "user", // "user" is a reserved keyword
|
||||
Columns: map[string]*models.Column{
|
||||
"id": {
|
||||
Name: "id",
|
||||
Type: "bigint",
|
||||
IsPrimaryKey: true,
|
||||
},
|
||||
"select": { // "select" is a reserved keyword
|
||||
Name: "select",
|
||||
Type: "varchar(50)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule Rule
|
||||
wantLen int
|
||||
checkPasses bool
|
||||
}{
|
||||
{
|
||||
name: "check tables only",
|
||||
rule: Rule{
|
||||
CheckTables: true,
|
||||
CheckColumns: false,
|
||||
Message: "Reserved keyword used",
|
||||
},
|
||||
wantLen: 1, // "user" table
|
||||
checkPasses: false,
|
||||
},
|
||||
{
|
||||
name: "check columns only",
|
||||
rule: Rule{
|
||||
CheckTables: false,
|
||||
CheckColumns: true,
|
||||
Message: "Reserved keyword used",
|
||||
},
|
||||
wantLen: 2, // "id", "select" columns (id passes, select fails)
|
||||
checkPasses: false,
|
||||
},
|
||||
{
|
||||
name: "check both",
|
||||
rule: Rule{
|
||||
CheckTables: true,
|
||||
CheckColumns: true,
|
||||
Message: "Reserved keyword used",
|
||||
},
|
||||
wantLen: 3, // "user" table + "id", "select" columns
|
||||
checkPasses: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := validateReservedKeywords(db, tt.rule, "test_rule")
|
||||
if len(results) != tt.wantLen {
|
||||
t.Errorf("validateReservedKeywords() returned %d results, want %d", len(results), tt.wantLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMissingPrimaryKey(t *testing.T) {
|
||||
// Create database with and without primary keys
|
||||
db := &models.Database{
|
||||
Name: "testdb",
|
||||
Schemas: []*models.Schema{
|
||||
{
|
||||
Name: "public",
|
||||
Tables: []*models.Table{
|
||||
{
|
||||
Name: "with_pk",
|
||||
Columns: map[string]*models.Column{
|
||||
"id": {
|
||||
Name: "id",
|
||||
Type: "bigint",
|
||||
IsPrimaryKey: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "without_pk",
|
||||
Columns: map[string]*models.Column{
|
||||
"name": {
|
||||
Name: "name",
|
||||
Type: "varchar(50)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rule := Rule{
|
||||
Message: "Table missing primary key",
|
||||
}
|
||||
|
||||
results := validateMissingPrimaryKey(db, rule, "test_rule")
|
||||
|
||||
if len(results) != 2 {
|
||||
t.Errorf("validateMissingPrimaryKey() returned %d results, want 2", len(results))
|
||||
}
|
||||
|
||||
// First result should pass (with_pk has PK)
|
||||
if results[0].Passed != true {
|
||||
t.Errorf("validateMissingPrimaryKey() result[0].Passed=%v, want true", results[0].Passed)
|
||||
}
|
||||
|
||||
// Second result should fail (without_pk missing PK)
|
||||
if results[1].Passed != false {
|
||||
t.Errorf("validateMissingPrimaryKey() result[1].Passed=%v, want false", results[1].Passed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateOrphanedForeignKey(t *testing.T) {
|
||||
// Create database with orphaned FK
|
||||
db := &models.Database{
|
||||
Name: "testdb",
|
||||
Schemas: []*models.Schema{
|
||||
{
|
||||
Name: "public",
|
||||
Tables: []*models.Table{
|
||||
{
|
||||
Name: "users",
|
||||
Columns: map[string]*models.Column{
|
||||
"id": {
|
||||
Name: "id",
|
||||
Type: "bigint",
|
||||
IsPrimaryKey: true,
|
||||
},
|
||||
},
|
||||
Constraints: map[string]*models.Constraint{
|
||||
"fk_nonexistent": {
|
||||
Name: "fk_nonexistent",
|
||||
Type: models.ForeignKeyConstraint,
|
||||
Columns: []string{"rid_organization"},
|
||||
ReferencedTable: "nonexistent_table",
|
||||
ReferencedSchema: "public",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rule := Rule{
|
||||
Message: "Foreign key references non-existent table",
|
||||
}
|
||||
|
||||
results := validateOrphanedForeignKey(db, rule, "test_rule")
|
||||
|
||||
if len(results) != 1 {
|
||||
t.Errorf("validateOrphanedForeignKey() returned %d results, want 1", len(results))
|
||||
}
|
||||
|
||||
if results[0].Passed != false {
|
||||
t.Errorf("validateOrphanedForeignKey() passed=%v, want false", results[0].Passed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCircularDependency(t *testing.T) {
|
||||
// Create database with circular dependency
|
||||
db := &models.Database{
|
||||
Name: "testdb",
|
||||
Schemas: []*models.Schema{
|
||||
{
|
||||
Name: "public",
|
||||
Tables: []*models.Table{
|
||||
{
|
||||
Name: "table_a",
|
||||
Columns: map[string]*models.Column{
|
||||
"id": {Name: "id", Type: "bigint", IsPrimaryKey: true},
|
||||
},
|
||||
Constraints: map[string]*models.Constraint{
|
||||
"fk_to_b": {
|
||||
Name: "fk_to_b",
|
||||
Type: models.ForeignKeyConstraint,
|
||||
ReferencedTable: "table_b",
|
||||
ReferencedSchema: "public",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "table_b",
|
||||
Columns: map[string]*models.Column{
|
||||
"id": {Name: "id", Type: "bigint", IsPrimaryKey: true},
|
||||
},
|
||||
Constraints: map[string]*models.Constraint{
|
||||
"fk_to_a": {
|
||||
Name: "fk_to_a",
|
||||
Type: models.ForeignKeyConstraint,
|
||||
ReferencedTable: "table_a",
|
||||
ReferencedSchema: "public",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rule := Rule{
|
||||
Message: "Circular dependency detected",
|
||||
}
|
||||
|
||||
results := validateCircularDependency(db, rule, "test_rule")
|
||||
|
||||
// Should detect circular dependency in both tables
|
||||
if len(results) == 0 {
|
||||
t.Error("validateCircularDependency() returned 0 results, expected circular dependency detection")
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
if result.Passed {
|
||||
t.Error("validateCircularDependency() passed=true, want false for circular dependency")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeDataType(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"varchar(50)", "varchar"},
|
||||
{"decimal(10,2)", "decimal"},
|
||||
{"int", "int"},
|
||||
{"BIGINT", "bigint"},
|
||||
{"VARCHAR(255)", "varchar"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
result := normalizeDataType(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("normalizeDataType(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
slice []string
|
||||
value string
|
||||
expected bool
|
||||
}{
|
||||
{"found exact", []string{"foo", "bar", "baz"}, "bar", true},
|
||||
{"not found", []string{"foo", "bar", "baz"}, "qux", false},
|
||||
{"case insensitive match", []string{"foo", "Bar", "baz"}, "bar", true},
|
||||
{"empty slice", []string{}, "foo", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := contains(tt.slice, tt.value)
|
||||
if result != tt.expected {
|
||||
t.Errorf("contains(%v, %q) = %v, want %v", tt.slice, tt.value, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasCycle(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
graph map[string][]string
|
||||
node string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "simple cycle",
|
||||
graph: map[string][]string{
|
||||
"A": {"B"},
|
||||
"B": {"C"},
|
||||
"C": {"A"},
|
||||
},
|
||||
node: "A",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "no cycle",
|
||||
graph: map[string][]string{
|
||||
"A": {"B"},
|
||||
"B": {"C"},
|
||||
"C": {},
|
||||
},
|
||||
node: "A",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "self cycle",
|
||||
graph: map[string][]string{
|
||||
"A": {"A"},
|
||||
},
|
||||
node: "A",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
visited := make(map[string]bool)
|
||||
recStack := make(map[string]bool)
|
||||
result := hasCycle(tt.node, tt.graph, visited, recStack)
|
||||
if result != tt.expected {
|
||||
t.Errorf("hasCycle() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatLocation(t *testing.T) {
|
||||
tests := []struct {
|
||||
schema string
|
||||
table string
|
||||
column string
|
||||
expected string
|
||||
}{
|
||||
{"public", "users", "id", "public.users.id"},
|
||||
{"public", "users", "", "public.users"},
|
||||
{"public", "", "", "public"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.expected, func(t *testing.T) {
|
||||
result := formatLocation(tt.schema, tt.table, tt.column)
|
||||
if result != tt.expected {
|
||||
t.Errorf("formatLocation(%q, %q, %q) = %q, want %q",
|
||||
tt.schema, tt.table, tt.column, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user