456 lines
12 KiB
Go
456 lines
12 KiB
Go
package interpreter
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestCustomJavaScriptFunctions(t *testing.T) {
|
|
// Create a temporary directory for test files
|
|
tempDir := t.TempDir()
|
|
|
|
// Create a test Masonry file
|
|
masonryContent := `entity User {
|
|
name: string required
|
|
email: string required unique
|
|
}
|
|
|
|
entity Post {
|
|
title: string required
|
|
slug: string required
|
|
authorId: uuid required
|
|
}`
|
|
|
|
masonryFile := filepath.Join(tempDir, "test.masonry")
|
|
err := os.WriteFile(masonryFile, []byte(masonryContent), 0644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write test Masonry file: %v", err)
|
|
}
|
|
|
|
// Create templates directory
|
|
templatesDir := filepath.Join(tempDir, "templates")
|
|
err = os.MkdirAll(templatesDir, 0755)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create templates directory: %v", err)
|
|
}
|
|
|
|
// Create a test template that uses custom functions
|
|
templateContent := `package models
|
|
|
|
// {{formatComment "Generated model"}}
|
|
type {{.Entity.Name | title}} struct {
|
|
Name string
|
|
}
|
|
|
|
// Table name: {{pluralize (.Entity.Name | lower)}}
|
|
func ({{.Entity.Name | title}}) TableName() string {
|
|
return "{{pluralize (.Entity.Name | lower)}}"
|
|
}
|
|
|
|
// Custom function result: {{customUpper (.Entity.Name)}}`
|
|
|
|
templateFile := filepath.Join(templatesDir, "model.tmpl")
|
|
err = os.WriteFile(templateFile, []byte(templateContent), 0644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write test template file: %v", err)
|
|
}
|
|
|
|
// Create manifest with custom JavaScript functions
|
|
manifestContent := `name: "Test Generator"
|
|
description: "Test custom JavaScript functions"
|
|
|
|
functions:
|
|
# Format a comment block
|
|
formatComment: |
|
|
function main() {
|
|
var text = args[0] || "";
|
|
return "// " + text;
|
|
}
|
|
|
|
# Convert singular words to plural (simple rules)
|
|
pluralize: |
|
|
function main() {
|
|
var word = args[0] || "";
|
|
if (word.slice(-1) === 'y') {
|
|
return word.slice(0, -1) + 'ies';
|
|
} else if (word.slice(-1) === 's') {
|
|
return word + 'es';
|
|
} else {
|
|
return word + 's';
|
|
}
|
|
}
|
|
|
|
# Convert to uppercase
|
|
customUpper: |
|
|
function main() {
|
|
var text = args[0] || "";
|
|
return text.toUpperCase();
|
|
}
|
|
|
|
outputs:
|
|
- path: "models/{{.Entity.Name | lower}}.go"
|
|
template: "model"
|
|
iterator: "entities"
|
|
item_context: "Entity"`
|
|
|
|
manifestFile := filepath.Join(templatesDir, "manifest.yaml")
|
|
err = os.WriteFile(manifestFile, []byte(manifestContent), 0644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write test manifest file: %v", err)
|
|
}
|
|
|
|
// Create template interpreter and test
|
|
interpreter := NewTemplateInterpreter()
|
|
|
|
// Test InterpretToFiles with custom functions
|
|
outputs, err := interpreter.InterpretToFiles(masonryFile, templatesDir, "manifest.yaml")
|
|
if err != nil {
|
|
t.Fatalf("InterpretToFiles failed: %v", err)
|
|
}
|
|
|
|
// Should generate 2 files (User and Post)
|
|
expectedFiles := 2
|
|
if len(outputs) != expectedFiles {
|
|
t.Errorf("Expected %d output files, got %d", expectedFiles, len(outputs))
|
|
}
|
|
|
|
// Check User model output
|
|
userFile := "models/user.go"
|
|
userContent, exists := outputs[userFile]
|
|
if !exists {
|
|
t.Errorf("Expected output file %s not found", userFile)
|
|
} else {
|
|
// Test formatComment function
|
|
if !strings.Contains(userContent, "// Generated model") {
|
|
t.Errorf("formatComment function not working: expected '// Generated model' in output")
|
|
}
|
|
|
|
// Test pluralize function
|
|
if !strings.Contains(userContent, "Table name: users") {
|
|
t.Errorf("pluralize function not working: expected 'Table name: users' in output")
|
|
}
|
|
if !strings.Contains(userContent, `return "users"`) {
|
|
t.Errorf("pluralize function not working in template: expected 'return \"users\"' in output")
|
|
}
|
|
|
|
// Test customUpper function
|
|
if !strings.Contains(userContent, "Custom function result: USER") {
|
|
t.Errorf("customUpper function not working: expected 'Custom function result: USER' in output")
|
|
}
|
|
}
|
|
|
|
// Check Post model output
|
|
postFile := "models/post.go"
|
|
postContent, exists := outputs[postFile]
|
|
if !exists {
|
|
t.Errorf("Expected output file %s not found", postFile)
|
|
} else {
|
|
// Test pluralize function with 's' ending
|
|
if !strings.Contains(postContent, "Table name: posts") {
|
|
t.Errorf("pluralize function not working for 's' ending: expected 'Table name: posts' in output")
|
|
}
|
|
if !strings.Contains(postContent, `return "posts"`) {
|
|
t.Errorf("pluralize function not working in template for 's' ending: expected 'return \"posts\"' in output")
|
|
}
|
|
|
|
// Test customUpper function
|
|
if !strings.Contains(postContent, "Custom function result: POST") {
|
|
t.Errorf("customUpper function not working: expected 'Custom function result: POST' in output")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCustomFunctionErrorHandling(t *testing.T) {
|
|
interpreter := NewTemplateInterpreter()
|
|
|
|
tests := []struct {
|
|
name string
|
|
functionName string
|
|
jsCode string
|
|
expectError bool
|
|
errorContains string
|
|
}{
|
|
{
|
|
name: "Invalid function name",
|
|
functionName: "123invalid",
|
|
jsCode: "function main() { return 'test'; }",
|
|
expectError: true,
|
|
errorContains: "invalid function name",
|
|
},
|
|
{
|
|
name: "Reserved function name",
|
|
functionName: "and",
|
|
jsCode: "function main() { return 'test'; }",
|
|
expectError: true,
|
|
errorContains: "invalid function name",
|
|
},
|
|
{
|
|
name: "Missing main function",
|
|
functionName: "testFunc",
|
|
jsCode: "function notMain() { return 'test'; }",
|
|
expectError: false, // Error will occur during execution, not loading
|
|
},
|
|
{
|
|
name: "Valid function",
|
|
functionName: "validFunc",
|
|
jsCode: "function main() { return 'test'; }",
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
functions := map[string]string{
|
|
tt.functionName: tt.jsCode,
|
|
}
|
|
|
|
err := interpreter.loadCustomFunctions(functions)
|
|
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Errorf("Expected error but got none")
|
|
} else if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) {
|
|
t.Errorf("Expected error to contain '%s', got: %v", tt.errorContains, err)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJavaScriptFunctionExecution(t *testing.T) {
|
|
interpreter := NewTemplateInterpreter()
|
|
|
|
// Load a test function
|
|
functions := map[string]string{
|
|
"testConcat": `
|
|
function main() {
|
|
var result = "";
|
|
for (var i = 0; i < args.length; i++) {
|
|
result += args[i];
|
|
if (i < args.length - 1) result += "-";
|
|
}
|
|
return result;
|
|
}
|
|
`,
|
|
"testMath": `
|
|
function main() {
|
|
var a = args[0] || 0;
|
|
var b = args[1] || 0;
|
|
return a + b;
|
|
}
|
|
`,
|
|
"testError": `
|
|
function main() {
|
|
throw new Error("Test error");
|
|
}
|
|
`,
|
|
"testNoMain": `
|
|
function notMain() {
|
|
return "should not work";
|
|
}
|
|
`,
|
|
}
|
|
|
|
err := interpreter.loadCustomFunctions(functions)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load custom functions: %v", err)
|
|
}
|
|
|
|
// Test function execution through template
|
|
testCases := []struct {
|
|
name string
|
|
template string
|
|
data interface{}
|
|
expectedOutput string
|
|
expectError bool
|
|
errorContains string
|
|
}{
|
|
{
|
|
name: "Concat function with multiple args",
|
|
template: `{{testConcat "hello" "world" "test"}}`,
|
|
data: struct{}{},
|
|
expectedOutput: "hello-world-test",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Math function",
|
|
template: `{{testMath 5 3}}`,
|
|
data: struct{}{},
|
|
expectedOutput: "8",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Function with error",
|
|
template: `{{testError}}`,
|
|
data: struct{}{},
|
|
expectError: true,
|
|
errorContains: "Test error",
|
|
},
|
|
{
|
|
name: "Function without main",
|
|
template: `{{testNoMain}}`,
|
|
data: struct{}{},
|
|
expectError: true,
|
|
errorContains: "must define a main() function",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := interpreter.Interpret("", tc.template)
|
|
|
|
if tc.expectError {
|
|
if err == nil {
|
|
t.Errorf("Expected error but got none")
|
|
} else if tc.errorContains != "" && !strings.Contains(err.Error(), tc.errorContains) {
|
|
t.Errorf("Expected error to contain '%s', got: %v", tc.errorContains, err)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
} else if result != tc.expectedOutput {
|
|
t.Errorf("Expected output '%s', got '%s'", tc.expectedOutput, result)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCustomFunctionArgumentHandling(t *testing.T) {
|
|
interpreter := NewTemplateInterpreter()
|
|
|
|
// Load test functions that handle different argument types
|
|
functions := map[string]string{
|
|
"argCount": `
|
|
function main() {
|
|
return args.length;
|
|
}
|
|
`,
|
|
"argTypes": `
|
|
function main() {
|
|
var types = [];
|
|
for (var i = 0; i < args.length; i++) {
|
|
types.push(typeof args[i]);
|
|
}
|
|
return types.join(",");
|
|
}
|
|
`,
|
|
"argAccess": `
|
|
function main() {
|
|
// Test both args array and individual arg variables
|
|
var fromArray = args[0] || "empty";
|
|
var fromVar = arg0 || "empty";
|
|
return fromArray + ":" + fromVar;
|
|
}
|
|
`,
|
|
}
|
|
|
|
err := interpreter.loadCustomFunctions(functions)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load custom functions: %v", err)
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
template string
|
|
expectedOutput string
|
|
}{
|
|
{
|
|
name: "No arguments",
|
|
template: `{{argCount}}`,
|
|
expectedOutput: "0",
|
|
},
|
|
{
|
|
name: "Multiple arguments",
|
|
template: `{{argCount "a" "b" "c"}}`,
|
|
expectedOutput: "3",
|
|
},
|
|
{
|
|
name: "Argument types",
|
|
template: `{{argTypes "string" 42}}`,
|
|
expectedOutput: "string,number",
|
|
},
|
|
{
|
|
name: "Argument access methods",
|
|
template: `{{argAccess "test"}}`,
|
|
expectedOutput: "test:test",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := interpreter.Interpret("", tc.template)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
} else if result != tc.expectedOutput {
|
|
t.Errorf("Expected output '%s', got '%s'", tc.expectedOutput, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFunctionLoadingOrder(t *testing.T) {
|
|
// This test ensures that custom functions are loaded before templates
|
|
// to prevent the "function not defined" error that was fixed
|
|
|
|
tempDir := t.TempDir()
|
|
templatesDir := filepath.Join(tempDir, "templates")
|
|
err := os.MkdirAll(templatesDir, 0755)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create templates directory: %v", err)
|
|
}
|
|
|
|
// Create a template that uses a custom function
|
|
templateContent := `{{customFunc "test"}}`
|
|
templateFile := filepath.Join(templatesDir, "test.tmpl")
|
|
err = os.WriteFile(templateFile, []byte(templateContent), 0644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write template file: %v", err)
|
|
}
|
|
|
|
// Create manifest with the custom function
|
|
manifestContent := `name: "Order Test"
|
|
functions:
|
|
customFunc: |
|
|
function main() {
|
|
return "custom:" + args[0];
|
|
}
|
|
outputs:
|
|
- path: "test.txt"
|
|
template: "test"`
|
|
|
|
manifestFile := filepath.Join(templatesDir, "manifest.yaml")
|
|
err = os.WriteFile(manifestFile, []byte(manifestContent), 0644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write manifest file: %v", err)
|
|
}
|
|
|
|
// Create empty Masonry file
|
|
masonryFile := filepath.Join(tempDir, "empty.masonry")
|
|
err = os.WriteFile(masonryFile, []byte(""), 0644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write Masonry file: %v", err)
|
|
}
|
|
|
|
// Test that InterpretToFiles works (this would fail if functions aren't loaded first)
|
|
interpreter := NewTemplateInterpreter()
|
|
outputs, err := interpreter.InterpretToFiles(masonryFile, templatesDir, "manifest.yaml")
|
|
if err != nil {
|
|
t.Fatalf("InterpretToFiles failed: %v", err)
|
|
}
|
|
|
|
// Check that the custom function was executed
|
|
testContent, exists := outputs["test.txt"]
|
|
if !exists {
|
|
t.Errorf("Expected output file test.txt not found")
|
|
} else if testContent != "custom:test" {
|
|
t.Errorf("Expected 'custom:test', got '%s'", testContent)
|
|
}
|
|
}
|