300 lines
8.8 KiB
Go
300 lines
8.8 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/entity-maker/entity-maker/internal/database"
|
|
"github.com/entity-maker/entity-maker/internal/generator"
|
|
)
|
|
|
|
// Note: The cmd/entity-maker package has low test coverage by design.
|
|
// The run() function is integration code that orchestrates calls to:
|
|
// - Prompt functions (requires user input)
|
|
// - Database connections (requires live PostgreSQL)
|
|
// - File system operations (requires specific paths)
|
|
//
|
|
// All the business logic is tested in the internal/* packages where
|
|
// coverage is high (91-97%). The tests below validate the file generation
|
|
// workflow in isolation without needing the full integration environment.
|
|
|
|
// TestPackageCompiles validates that the package compiles correctly
|
|
func TestPackageCompiles(t *testing.T) {
|
|
// This test ensures the main package compiles correctly
|
|
// The main() function itself is hard to test as it calls os.Exit()
|
|
// and depends on external resources (database, user input, file system)
|
|
// If this test runs, the package compiled successfully
|
|
t.Log("Main package compiled successfully")
|
|
}
|
|
|
|
// TestFileGeneration tests the file generation logic in isolation
|
|
func TestFileGeneration(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tmpDir := t.TempDir()
|
|
moduleName := "test_entity"
|
|
moduleDir := filepath.Join(tmpDir, moduleName)
|
|
|
|
// Create the module directory
|
|
if err := os.MkdirAll(moduleDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create module directory: %v", err)
|
|
}
|
|
|
|
// Create a simple test context
|
|
tableInfo := &database.TableInfo{
|
|
Schema: "public",
|
|
TableName: "test_table",
|
|
Columns: []database.Column{
|
|
{Name: "id", DataType: "integer", IsPrimaryKey: true, IsNullable: false},
|
|
{Name: "name", DataType: "varchar", IsNullable: false},
|
|
},
|
|
ForeignKeys: []database.ForeignKey{},
|
|
EnumTypes: map[string]database.EnumType{},
|
|
}
|
|
|
|
ctx := generator.NewContext(tableInfo, "")
|
|
|
|
// Define files to generate (same as in main.go)
|
|
files := map[string]func(*generator.Context) (string, error){
|
|
"table.py": generator.GenerateTable,
|
|
"model.py": generator.GenerateModel,
|
|
"filter.py": generator.GenerateFilter,
|
|
"load_options.py": generator.GenerateLoadOptions,
|
|
"repository.py": generator.GenerateRepository,
|
|
"manager.py": generator.GenerateManager,
|
|
"factory.py": generator.GenerateFactory,
|
|
"mapper.py": generator.GenerateMapper,
|
|
"__init__.py": generator.GenerateInit,
|
|
}
|
|
|
|
// Generate and write each file
|
|
for filename, genFunc := range files {
|
|
content, err := genFunc(ctx)
|
|
if err != nil {
|
|
t.Errorf("Failed to generate %s: %v", filename, err)
|
|
continue
|
|
}
|
|
|
|
filePath := filepath.Join(moduleDir, filename)
|
|
if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
|
|
t.Errorf("Failed to write %s: %v", filename, err)
|
|
continue
|
|
}
|
|
|
|
// Verify file was created
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
t.Errorf("File %s was not created", filename)
|
|
}
|
|
|
|
// Verify file has content
|
|
if len(content) == 0 && filename != "__init__.py" {
|
|
t.Errorf("File %s has no content", filename)
|
|
}
|
|
}
|
|
|
|
// Verify all expected files exist
|
|
expectedFiles := []string{
|
|
"table.py", "model.py", "filter.py", "load_options.py",
|
|
"repository.py", "manager.py", "factory.py", "mapper.py", "__init__.py",
|
|
}
|
|
|
|
for _, filename := range expectedFiles {
|
|
filePath := filepath.Join(moduleDir, filename)
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
t.Errorf("Expected file %s does not exist", filename)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestFileGenerationWithEnums tests file generation when enum types are present
|
|
func TestFileGenerationWithEnums(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
moduleName := "test_entity"
|
|
moduleDir := filepath.Join(tmpDir, moduleName)
|
|
|
|
if err := os.MkdirAll(moduleDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create module directory: %v", err)
|
|
}
|
|
|
|
// Create test context with enums
|
|
tableInfo := &database.TableInfo{
|
|
Schema: "public",
|
|
TableName: "test_table",
|
|
Columns: []database.Column{
|
|
{Name: "id", DataType: "integer", IsPrimaryKey: true, IsNullable: false},
|
|
{Name: "status", DataType: "USER-DEFINED", UdtName: "status_enum", IsNullable: false},
|
|
},
|
|
ForeignKeys: []database.ForeignKey{},
|
|
EnumTypes: map[string]database.EnumType{
|
|
"status_enum": {
|
|
TypeName: "status_enum",
|
|
Values: []string{"active", "inactive", "pending"},
|
|
},
|
|
},
|
|
}
|
|
|
|
ctx := generator.NewContext(tableInfo, "")
|
|
|
|
files := map[string]func(*generator.Context) (string, error){
|
|
"table.py": generator.GenerateTable,
|
|
"model.py": generator.GenerateModel,
|
|
"filter.py": generator.GenerateFilter,
|
|
"load_options.py": generator.GenerateLoadOptions,
|
|
"repository.py": generator.GenerateRepository,
|
|
"manager.py": generator.GenerateManager,
|
|
"factory.py": generator.GenerateFactory,
|
|
"mapper.py": generator.GenerateMapper,
|
|
"__init__.py": generator.GenerateInit,
|
|
}
|
|
|
|
// Add enum.py since we have enum types
|
|
if len(tableInfo.EnumTypes) > 0 {
|
|
files["enum.py"] = generator.GenerateEnum
|
|
}
|
|
|
|
// Generate all files
|
|
for filename, genFunc := range files {
|
|
content, err := genFunc(ctx)
|
|
if err != nil {
|
|
t.Errorf("Failed to generate %s: %v", filename, err)
|
|
continue
|
|
}
|
|
|
|
filePath := filepath.Join(moduleDir, filename)
|
|
if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
|
|
t.Errorf("Failed to write %s: %v", filename, err)
|
|
}
|
|
}
|
|
|
|
// Verify enum.py was created
|
|
enumPath := filepath.Join(moduleDir, "enum.py")
|
|
if _, err := os.Stat(enumPath); os.IsNotExist(err) {
|
|
t.Error("enum.py was not created when enum types are present")
|
|
}
|
|
}
|
|
|
|
// TestModuleDirectoryCreation tests directory creation logic
|
|
func TestModuleDirectoryCreation(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
tests := []struct {
|
|
name string
|
|
moduleName string
|
|
expectErr bool
|
|
}{
|
|
{
|
|
name: "simple module name",
|
|
moduleName: "user",
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "nested module name",
|
|
moduleName: filepath.Join("nested", "module"),
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "module with underscore",
|
|
moduleName: "user_account",
|
|
expectErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
moduleDir := filepath.Join(tmpDir, tt.moduleName)
|
|
err := os.MkdirAll(moduleDir, 0755)
|
|
|
|
if (err != nil) != tt.expectErr {
|
|
t.Errorf("MkdirAll() error = %v, expectErr %v", err, tt.expectErr)
|
|
}
|
|
|
|
if !tt.expectErr {
|
|
// Verify directory exists
|
|
info, err := os.Stat(moduleDir)
|
|
if err != nil {
|
|
t.Errorf("Directory should exist: %v", err)
|
|
}
|
|
if !info.IsDir() {
|
|
t.Error("Path should be a directory")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGeneratedFilePermissions tests that generated files have correct permissions
|
|
func TestGeneratedFilePermissions(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFile := filepath.Join(tmpDir, "test.py")
|
|
|
|
content := "# Test content\n"
|
|
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
|
|
t.Fatalf("Failed to write test file: %v", err)
|
|
}
|
|
|
|
info, err := os.Stat(testFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to stat test file: %v", err)
|
|
}
|
|
|
|
mode := info.Mode()
|
|
|
|
// Check that owner can read and write
|
|
if mode&0600 != 0600 {
|
|
t.Error("File should be readable and writable by owner")
|
|
}
|
|
|
|
// Check that file is not executable
|
|
if mode&0111 != 0 {
|
|
t.Error("Python files should not be executable")
|
|
}
|
|
}
|
|
|
|
// TestGeneratedFilesAreNonEmpty tests that generated files have content
|
|
func TestGeneratedFilesAreNonEmpty(t *testing.T) {
|
|
tableInfo := &database.TableInfo{
|
|
Schema: "public",
|
|
TableName: "users",
|
|
Columns: []database.Column{
|
|
{Name: "id", DataType: "integer", IsPrimaryKey: true, IsNullable: false},
|
|
{Name: "name", DataType: "varchar", IsNullable: false},
|
|
{Name: "email", DataType: "varchar", IsNullable: true},
|
|
},
|
|
ForeignKeys: []database.ForeignKey{},
|
|
EnumTypes: map[string]database.EnumType{},
|
|
}
|
|
|
|
ctx := generator.NewContext(tableInfo, "")
|
|
|
|
generators := map[string]func(*generator.Context) (string, error){
|
|
"table": generator.GenerateTable,
|
|
"model": generator.GenerateModel,
|
|
"filter": generator.GenerateFilter,
|
|
"load_options": generator.GenerateLoadOptions,
|
|
"repository": generator.GenerateRepository,
|
|
"manager": generator.GenerateManager,
|
|
"factory": generator.GenerateFactory,
|
|
"mapper": generator.GenerateMapper,
|
|
}
|
|
|
|
for name, genFunc := range generators {
|
|
t.Run(name, func(t *testing.T) {
|
|
content, err := genFunc(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate %s: %v", name, err)
|
|
}
|
|
|
|
if len(content) == 0 {
|
|
t.Errorf("Generated %s content should not be empty", name)
|
|
}
|
|
|
|
// Check that content has at least some basic Python structure
|
|
if name != "mapper" { // mapper is just a snippet
|
|
if len(content) < 10 {
|
|
t.Errorf("Generated %s content seems too short: %d bytes", name, len(content))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|