You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
4.0KB

  1. package mapping
  2. import (
  3. "context"
  4. "encoding/json"
  5. "net/http"
  6. "net/http/httptest"
  7. "strings"
  8. "testing"
  9. "time"
  10. "qctextbuilder/internal/domain"
  11. "qctextbuilder/internal/llmruntime"
  12. )
  13. type stubSettingsReader struct {
  14. settings *domain.AppSettings
  15. err error
  16. }
  17. func (s *stubSettingsReader) GetSettings(context.Context) (*domain.AppSettings, error) {
  18. if s.err != nil {
  19. return nil, s.err
  20. }
  21. return s.settings, nil
  22. }
  23. func TestProviderAwareSuggestionGenerator_UsesActiveProviderModelAndKey(t *testing.T) {
  24. t.Parallel()
  25. var (
  26. gotPath string
  27. gotAuth string
  28. gotModel string
  29. gotTemperature float64
  30. gotMaxTokens float64
  31. )
  32. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  33. gotPath = r.URL.Path
  34. gotAuth = r.Header.Get("Authorization")
  35. var payload map[string]any
  36. _ = json.NewDecoder(r.Body).Decode(&payload)
  37. gotModel, _ = payload["model"].(string)
  38. gotTemperature, _ = payload["temperature"].(float64)
  39. gotMaxTokens, _ = payload["max_tokens"].(float64)
  40. _, _ = w.Write([]byte(`{"choices":[{"message":{"content":"{\"suggestions\":[{\"fieldPath\":\"text.textTitle_m1710_1\",\"value\":\"Provider Hero\",\"reason\":\"focused hero\"}]}"}}]}`))
  41. }))
  42. defer server.Close()
  43. generator := NewProviderAwareSuggestionGenerator(&stubSettingsReader{settings: &domain.AppSettings{
  44. LLMActiveProvider: domain.LLMProviderOpenAI,
  45. LLMActiveModel: "gpt-5.4",
  46. LLMBaseURL: server.URL,
  47. LLMTemperature: 0.65,
  48. LLMMaxTokens: 333,
  49. OpenAIAPIKeyEncrypted: "openai-key",
  50. }}, llmruntime.NewFactory(5*time.Second))
  51. result, err := generator.Generate(context.Background(), SuggestionRequest{
  52. Fields: []domain.TemplateField{
  53. {Path: "text.textTitle_m1710_1", KeyName: "textTitle_m1710_1", FieldKind: "text", IsEnabled: true, WebsiteSection: domain.WebsiteSectionHero},
  54. },
  55. GlobalData: map[string]any{"companyName": "Muster AG"},
  56. Existing: map[string]string{},
  57. })
  58. if err != nil {
  59. t.Fatalf("generate failed: %v", err)
  60. }
  61. if got := result.ByFieldPath["text.textTitle_m1710_1"].Value; got != "Provider Hero" {
  62. t.Fatalf("unexpected value: %q", got)
  63. }
  64. if got := result.ByFieldPath["text.textTitle_m1710_1"].Source; got != domain.LLMProviderOpenAI {
  65. t.Fatalf("unexpected source: %q", got)
  66. }
  67. if gotPath != "/v1/chat/completions" {
  68. t.Fatalf("unexpected path: %s", gotPath)
  69. }
  70. if gotAuth != "Bearer openai-key" {
  71. t.Fatalf("unexpected auth header: %q", gotAuth)
  72. }
  73. if gotModel != "gpt-5.4" {
  74. t.Fatalf("unexpected model: %q", gotModel)
  75. }
  76. if gotTemperature != 0.65 {
  77. t.Fatalf("unexpected temperature: %v", gotTemperature)
  78. }
  79. if gotMaxTokens != 333 {
  80. t.Fatalf("unexpected max_tokens: %v", gotMaxTokens)
  81. }
  82. }
  83. func TestProviderAwareSuggestionGenerator_RequiresAPIKeyForNonOllama(t *testing.T) {
  84. t.Parallel()
  85. generator := NewProviderAwareSuggestionGenerator(&stubSettingsReader{settings: &domain.AppSettings{
  86. LLMActiveProvider: domain.LLMProviderAnthropic,
  87. LLMActiveModel: "claude-sonnet-4-5",
  88. }}, llmruntime.NewFactory(5*time.Second))
  89. _, err := generator.Generate(context.Background(), SuggestionRequest{
  90. Fields: []domain.TemplateField{{Path: "text.textTitle_m1710_1", KeyName: "textTitle_m1710_1", FieldKind: "text", IsEnabled: true, WebsiteSection: domain.WebsiteSectionHero}},
  91. })
  92. if err == nil || !strings.Contains(err.Error(), "api key") {
  93. t.Fatalf("expected api key error, got: %v", err)
  94. }
  95. }
  96. func TestParseProviderSuggestions_AcceptsFencedJSON(t *testing.T) {
  97. t.Parallel()
  98. items, err := parseProviderSuggestions("```json\n{\"suggestions\":[{\"fieldPath\":\"a\",\"value\":\"b\"}]}\n```")
  99. if err != nil {
  100. t.Fatalf("parse failed: %v", err)
  101. }
  102. if len(items) != 1 || items[0].FieldPath != "a" || items[0].Value != "b" {
  103. t.Fatalf("unexpected parsed result: %+v", items)
  104. }
  105. }
  106. func TestParseProviderSuggestions_RejectsEmptyValue(t *testing.T) {
  107. t.Parallel()
  108. _, err := parseProviderSuggestions(`{"suggestions":[{"fieldPath":"a","value":""}]}`)
  109. if err == nil || !strings.Contains(err.Error(), "empty value") {
  110. t.Fatalf("expected empty value error, got: %v", err)
  111. }
  112. }