package templates import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestNewTemplateEngine(t *testing.T) { engine := NewTemplateEngine(50) assert.NotNil(t, engine) assert.Equal(t, 50, engine.maxSize) assert.Equal(t, 0, engine.CacheSize()) } func TestCompile(t *testing.T) { engine := NewTemplateEngine(50) tmpl, err := engine.Compile("test", "Hello {{.Name}}!") assert.NoError(t, err) assert.NotNil(t, tmpl) assert.True(t, engine.IsCached("test")) } func TestCompileDuplicate(t *testing.T) { engine := NewTemplateEngine(50) tmpl1, _ := engine.Compile("test", "Hello {{.Name}}!") tmpl2, _ := engine.Compile("test", "Hello {{.Name}}!") // Should return the same cached template assert.Equal(t, tmpl1, tmpl2) assert.Equal(t, 1, engine.CacheSize()) } func TestRender(t *testing.T) { engine := NewTemplateEngine(50) engine.Compile("test", "Hello {{.Name}}!") result, err := engine.Render("test", map[string]string{"Name": "World"}) assert.NoError(t, err) assert.Equal(t, "Hello World!", result) } func TestRenderNotFound(t *testing.T) { engine := NewTemplateEngine(50) _, err := engine.Render("nonexistent", map[string]string{}) assert.Error(t, err) } func TestCompileAndRender(t *testing.T) { engine := NewTemplateEngine(50) result, err := engine.CompileAndRender("test", "{{.X}} + {{.Y}} = {{.Z}}", map[string]int{ "X": 2, "Y": 3, "Z": 5, }) assert.NoError(t, err) assert.Equal(t, "2 + 3 = 5", result) } func TestRenderMultipleTimes(t *testing.T) { engine := NewTemplateEngine(50) engine.Compile("test", "Count: {{.}}") result1, _ := engine.Render("test", 1) result2, _ := engine.Render("test", 2) result3, _ := engine.Render("test", 3) assert.Equal(t, "Count: 1", result1) assert.Equal(t, "Count: 2", result2) assert.Equal(t, "Count: 3", result3) stats, _ := engine.GetStats("test") assert.Equal(t, 3, stats.RenderCount) } func TestGetStats(t *testing.T) { engine := NewTemplateEngine(50) engine.Compile("test", "Hello") stats, exists := engine.GetStats("test") assert.True(t, exists) assert.NotNil(t, stats) assert.Equal(t, "test", stats.TemplateName) assert.NotZero(t, stats.CompileTime) } func TestGetAllStats(t *testing.T) { engine := NewTemplateEngine(50) engine.Compile("test1", "Template 1") engine.Compile("test2", "Template 2") engine.Compile("test3", "Template 3") allStats := engine.GetAllStats() assert.Equal(t, 3, len(allStats)) assert.NotNil(t, allStats["test1"]) assert.NotNil(t, allStats["test2"]) assert.NotNil(t, allStats["test3"]) } func TestClear(t *testing.T) { engine := NewTemplateEngine(50) engine.Compile("test1", "Template 1") engine.Compile("test2", "Template 2") assert.Equal(t, 2, engine.CacheSize()) engine.Clear() assert.Equal(t, 0, engine.CacheSize()) assert.False(t, engine.IsCached("test1")) } func TestRemove(t *testing.T) { engine := NewTemplateEngine(50) engine.Compile("test1", "Template 1") engine.Compile("test2", "Template 2") assert.Equal(t, 2, engine.CacheSize()) engine.Remove("test1") assert.Equal(t, 1, engine.CacheSize()) assert.False(t, engine.IsCached("test1")) assert.True(t, engine.IsCached("test2")) } func TestCacheEviction(t *testing.T) { engine := NewTemplateEngine(3) engine.Compile("test1", "Template 1") engine.Compile("test2", "Template 2") engine.Compile("test3", "Template 3") assert.Equal(t, 3, engine.CacheSize()) // Adding a 4th template should evict the oldest (test1) time.Sleep(10 * time.Millisecond) engine.Compile("test4", "Template 4") assert.Equal(t, 3, engine.CacheSize()) assert.False(t, engine.IsCached("test1")) assert.True(t, engine.IsCached("test2")) assert.True(t, engine.IsCached("test3")) assert.True(t, engine.IsCached("test4")) } func TestIsCached(t *testing.T) { engine := NewTemplateEngine(50) assert.False(t, engine.IsCached("test")) engine.Compile("test", "Template") assert.True(t, engine.IsCached("test")) } func TestGetCacheStats(t *testing.T) { engine := NewTemplateEngine(50) engine.Compile("test1", "Template 1") engine.Render("test1", "data") engine.Compile("test2", "Template 2") engine.Render("test2", "data") engine.Render("test2", "data") stats := engine.GetCacheStats() assert.Equal(t, 2, stats["cache_size"]) assert.Equal(t, 50, stats["max_size"]) assert.Equal(t, 3, stats["total_renders"]) assert.NotZero(t, stats["total_render_time"]) } func TestComplexTemplate(t *testing.T) { engine := NewTemplateEngine(50) templateStr := ` {{range .Items}} - {{.Name}}: {{.Value}} {{end}} ` data := map[string]interface{}{ "Items": []map[string]interface{}{ {"Name": "Item1", "Value": 10}, {"Name": "Item2", "Value": 20}, }, } result, err := engine.CompileAndRender("list", templateStr, data) assert.NoError(t, err) assert.Contains(t, result, "Item1") assert.Contains(t, result, "Item2") } func TestRenderLatency(t *testing.T) { engine := NewTemplateEngine(50) engine.Compile("test", "Hello {{.Name}}!") start := time.Now() _, _ = engine.Render("test", map[string]string{"Name": "World"}) latency := time.Since(start) // Should be < 100ms even accounting for slow systems assert.Less(t, latency, 100*time.Millisecond) } func TestParseError(t *testing.T) { engine := NewTemplateEngine(50) _, err := engine.Compile("test", "{{.Name} missing closing bracket") assert.Error(t, err) } func BenchmarkRender(b *testing.B) { engine := NewTemplateEngine(50) engine.Compile("test", "Hello {{.Name}}!") for i := 0; i < b.N; i++ { engine.Render("test", map[string]string{"Name": "World"}) } } func BenchmarkCompileAndRender(b *testing.B) { engine := NewTemplateEngine(50) for i := 0; i < b.N; i++ { engine.CompileAndRender("test"+string(rune(i%10)), "Hello {{.}}", "World") } }