package prompt import ( "context" "errors" "testing" "gitea.maximumdirect.net/eric/scriptorium/internal/domain" ) func TestGoRenderer_Render(t *testing.T) { renderer := NewGoRenderer() ctx := context.Background() inputs := map[string]*domain.Artifact{ "transcript": {Body: []byte("The quick brown fox.")}, } vars := map[string]string{ "role": "helpful assistant", "tone": "concise", } t.Run("rendering inline message content", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ {Role: "user", Content: "Analyze this: {{input \"transcript\"}}"}, }, } res, err := renderer.Render(ctx, def, inputs, vars) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(res.Messages) != 1 { t.Fatalf("expected 1 message, got %d", len(res.Messages)) } if res.Messages[0].Content != "Analyze this: The quick brown fox." { t.Fatalf("unexpected rendered content: %q", res.Messages[0].Content) } }) t.Run("rendering file-backed message content loaded into prompt definition", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ {Role: "user", Content: "From file: {{input \"transcript\"}}", ContentFile: "/tmp/user.tmpl"}, }, } res, err := renderer.Render(ctx, def, inputs, vars) if err != nil { t.Fatalf("unexpected error: %v", err) } if got := res.Messages[0].Content; got != "From file: The quick brown fox." { t.Fatalf("unexpected file-backed render result: %q", got) } }) t.Run("rendering system and user messages", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ {Role: "system", Content: "You are a {{.role}}."}, {Role: "user", Content: "Analyze this: {{input \"transcript\"}}"}, }, } res, err := renderer.Render(ctx, def, inputs, vars) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(res.Messages) != 2 { t.Fatalf("expected 2 messages, got %d", len(res.Messages)) } if res.Messages[0].Role != "system" || res.Messages[1].Role != "user" { t.Fatalf("unexpected roles: %#v", res.Messages) } }) t.Run("copying cache control to rendered messages", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ { Role: "system", Content: "You are concise.", CacheControl: &domain.CacheControl{ Type: domain.CacheControlEphemeral, TTL: "1h", }, }, {Role: "user", Content: "Analyze this: {{input \"transcript\"}}"}, }, } res, err := renderer.Render(ctx, def, inputs, vars) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(res.Messages) != 2 { t.Fatalf("expected 2 messages, got %d", len(res.Messages)) } if res.Messages[0].CacheControl == nil { t.Fatal("expected rendered cache control") } if res.Messages[0].CacheControl.Type != domain.CacheControlEphemeral { t.Fatalf("unexpected cache control type: %q", res.Messages[0].CacheControl.Type) } if res.Messages[0].CacheControl.TTL != "1h" { t.Fatalf("unexpected cache control ttl: %q", res.Messages[0].CacheControl.TTL) } if res.Messages[1].CacheControl != nil { t.Fatalf("expected no cache control on second message, got %#v", res.Messages[1].CacheControl) } }) t.Run("rendered cache control does not alias source template", func(t *testing.T) { source := &domain.CacheControl{Type: domain.CacheControlEphemeral, TTL: "1h"} def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ {Role: "system", Content: "You are concise.", CacheControl: source}, }, } res, err := renderer.Render(ctx, def, inputs, vars) if err != nil { t.Fatalf("unexpected error: %v", err) } if res.Messages[0].CacheControl == source { t.Fatal("expected rendered cache control to be cloned") } res.Messages[0].CacheControl.TTL = "" if source.TTL != "1h" { t.Fatalf("source cache control was mutated, ttl=%q", source.TTL) } }) t.Run("accessing vars", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ {Role: "system", Content: "Speak in a {{.tone}} tone."}, }, } res, err := renderer.Render(ctx, def, inputs, vars) if err != nil { t.Fatalf("unexpected error: %v", err) } if res.Messages[0].Content != "Speak in a concise tone." { t.Fatalf("unexpected vars rendering: %q", res.Messages[0].Content) } }) t.Run("inserting required input artifact", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ {Role: "user", Content: "{{input \"transcript\"}}"}, }, } res, err := renderer.Render(ctx, def, inputs, vars) if err != nil { t.Fatalf("unexpected error: %v", err) } if res.Messages[0].Content != "The quick brown fox." { t.Fatalf("unexpected required input rendering: %q", res.Messages[0].Content) } }) t.Run("optional input absent and not referenced", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{ {Name: "transcript", Required: true}, {Name: "glossary", Required: false}, }, Templates: []domain.PromptMessageTemplate{ {Role: "user", Content: "Transcript: {{input \"transcript\"}}"}, }, } res, err := renderer.Render(ctx, def, inputs, vars) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(res.Messages) != 1 { t.Fatalf("expected one rendered message, got %d", len(res.Messages)) } }) t.Run("optional input absent but referenced, expecting failure", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{ {Name: "transcript", Required: true}, {Name: "glossary", Required: false}, }, Templates: []domain.PromptMessageTemplate{ {Role: "user", Content: "Glossary: {{input \"glossary\"}}"}, }, } _, err := renderer.Render(ctx, def, inputs, vars) if !errors.Is(err, ErrRenderFailure) { t.Fatalf("expected ErrRenderFailure, got %v", err) } if !errors.Is(err, ErrUnknownInput) { t.Fatalf("expected ErrUnknownInput, got %v", err) } }) t.Run("required input missing, expecting failure", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ {Role: "user", Content: "Analyze this: {{input \"transcript\"}}"}, }, } _, err := renderer.Render(ctx, def, map[string]*domain.Artifact{}, vars) if !errors.Is(err, ErrMissingRequiredInput) { t.Fatalf("expected ErrMissingRequiredInput, got %v", err) } }) t.Run("invalid template syntax", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ {Role: "user", Content: "Hello {{.unclosed"}, }, } _, err := renderer.Render(ctx, def, inputs, vars) if !errors.Is(err, ErrInvalidTemplate) { t.Fatalf("expected ErrInvalidTemplate, got %v", err) } }) t.Run("unknown input reference", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ {Role: "user", Content: "Hello {{input \"ghost\"}}"}, }, } _, err := renderer.Render(ctx, def, inputs, vars) if !errors.Is(err, ErrRenderFailure) { t.Fatalf("expected ErrRenderFailure, got %v", err) } if !errors.Is(err, ErrUnknownInput) { t.Fatalf("expected ErrUnknownInput, got %v", err) } }) t.Run("empty message role", func(t *testing.T) { def := &domain.PromptDefinition{ Inputs: []domain.PromptInput{{Name: "transcript", Required: true}}, Templates: []domain.PromptMessageTemplate{ {Role: "", Content: "Hello"}, }, } _, err := renderer.Render(ctx, def, inputs, vars) if !errors.Is(err, ErrInvalidMessageRole) { t.Fatalf("expected ErrInvalidMessageRole, got %v", err) } }) }