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.
838 lines
19 KiB
Go
838 lines
19 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|