mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 22:38:10 -05:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d25fbc04c | ||
|
|
4c822d2c59 | ||
|
|
f1ffd6ee29 | ||
|
|
deb59bdd21 | ||
|
|
2a1e8dcf12 | ||
|
|
b6fd81dd16 | ||
|
|
5b723c9e92 | ||
|
|
93f8978085 | ||
|
|
4d91bf837f | ||
|
|
cb29a0d606 | ||
|
|
b1eb7a82d9 | ||
|
|
bc8f5add00 | ||
|
|
c3f874f985 | ||
|
|
922df52d0c | ||
|
|
4badfecadb | ||
|
|
83139a64d5 | ||
|
|
78fd836532 | ||
|
|
894459ddec |
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -96,6 +96,7 @@
|
||||
"joho",
|
||||
"kballard",
|
||||
"Keploy",
|
||||
"kimi",
|
||||
"Kore",
|
||||
"ksylvan",
|
||||
"Langdock",
|
||||
@@ -151,6 +152,7 @@
|
||||
"Pulcherrima",
|
||||
"pycache",
|
||||
"pyperclip",
|
||||
"qwen",
|
||||
"readystream",
|
||||
"restapi",
|
||||
"rmextension",
|
||||
|
||||
48
CHANGELOG.md
48
CHANGELOG.md
@@ -1,5 +1,53 @@
|
||||
# Changelog
|
||||
|
||||
## v1.4.342 (2025-12-13)
|
||||
|
||||
### PR [#1866](https://github.com/danielmiessler/Fabric/pull/1866) by [ksylvan](https://github.com/ksylvan): fix: write CLI and streaming errors to stderr
|
||||
|
||||
- Fix: write CLI and streaming errors to stderr
|
||||
- Route CLI execution errors to standard error output
|
||||
- Print Anthropic stream errors to stderr consistently
|
||||
- Add os import to support stderr error writes
|
||||
- Preserve help-output suppression and exit behavior
|
||||
|
||||
## v1.4.341 (2025-12-10)
|
||||
|
||||
### PR [#1860](https://github.com/danielmiessler/Fabric/pull/1860) by [ksylvan](https://github.com/ksylvan): fix: allow resetting required settings without validation errors
|
||||
|
||||
- Fix: allow resetting required settings without validation errors
|
||||
- Update `Ask` to detect reset command and bypass validation
|
||||
- Refactor `OnAnswer` to support new `isReset` parameter logic
|
||||
- Invoke `ConfigureCustom` in `Setup` to avoid redundant re-validation
|
||||
- Add unit tests ensuring required fields can be reset
|
||||
|
||||
## v1.4.340 (2025-12-08)
|
||||
|
||||
### PR [#1856](https://github.com/danielmiessler/Fabric/pull/1856) by [ksylvan](https://github.com/ksylvan): Add support for new ClaudeHaiku 4.5 models
|
||||
|
||||
- Add support for new ClaudeHaiku models in client
|
||||
- Add `ModelClaudeHaiku4_5` to supported models
|
||||
- Add `ModelClaudeHaiku4_5_20251001` to supported models
|
||||
|
||||
## v1.4.339 (2025-12-08)
|
||||
|
||||
### PR [#1855](https://github.com/danielmiessler/Fabric/pull/1855) by [ksylvan](https://github.com/ksylvan): feat: add image attachment support for Ollama vision models
|
||||
|
||||
- Add multi-modal image support to Ollama client
|
||||
- Implement convertMessage to handle multi-content chat messages
|
||||
- Add loadImageBytes to fetch images from URLs
|
||||
- Support base64 data URLs for inline images
|
||||
- Handle HTTP image URLs with context propagation
|
||||
|
||||
## v1.4.338 (2025-12-04)
|
||||
|
||||
### PR [#1852](https://github.com/danielmiessler/Fabric/pull/1852) by [ksylvan](https://github.com/ksylvan): Add Abacus vendor for ChatLLM models with static model list
|
||||
|
||||
- Add static model support and register Abacus provider
|
||||
- Detect modelsURL starting with 'static:' and route appropriately
|
||||
- Implement getStaticModels returning curated Abacus model list
|
||||
- Register Abacus provider with ModelsURL 'static:abacus'
|
||||
- Extend provider tests to include Abacus existence
|
||||
|
||||
## v1.4.337 (2025-12-04)
|
||||
|
||||
### PR [#1851](https://github.com/danielmiessler/Fabric/pull/1851) by [ksylvan](https://github.com/ksylvan): Add Z AI provider and glm model support
|
||||
|
||||
@@ -73,6 +73,8 @@ Below are the **new features and capabilities** we've added (newest first):
|
||||
|
||||
### Recent Major Features
|
||||
|
||||
- [v1.4.338](https://github.com/danielmiessler/fabric/releases/tag/v1.4.338) (Dec 4, 2025) — Add Abacus vendor support for Chat-LLM
|
||||
models (see [RouteLLM APIs](https://abacus.ai/app/route-llm-apis)).
|
||||
- [v1.4.337](https://github.com/danielmiessler/fabric/releases/tag/v1.4.337) (Dec 4, 2025) — Add "Z AI" vendor support. See the [Z AI overview](https://docs.z.ai/guides/overview/overview) page for more details.
|
||||
- [v1.4.334](https://github.com/danielmiessler/fabric/releases/tag/v1.4.334) (Nov 26, 2025) — **Claude Opus 4.5**: Updates the Anthropic SDK to the latest and adds the new [Claude Opus 4.5](https://www.anthropic.com/news/claude-opus-4-5) to the available models.
|
||||
- [v1.4.331](https://github.com/danielmiessler/fabric/releases/tag/v1.4.331) (Nov 23, 2025) — **Support for GitHub Models**: Adds support for using GitHub Models.
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func main() {
|
||||
err := cli.Cli(version)
|
||||
if err != nil && !flags.WroteHelp(err) {
|
||||
fmt.Printf("%s\n", err)
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.337"
|
||||
var version = "v1.4.342"
|
||||
|
||||
Binary file not shown.
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -52,6 +53,8 @@ func NewClient() (ret *Client) {
|
||||
string(anthropic.ModelClaudeSonnet4_5_20250929),
|
||||
string(anthropic.ModelClaudeOpus4_5_20251101),
|
||||
string(anthropic.ModelClaudeOpus4_5),
|
||||
string(anthropic.ModelClaudeHaiku4_5),
|
||||
string(anthropic.ModelClaudeHaiku4_5_20251001),
|
||||
}
|
||||
|
||||
ret.modelBetas = map[string][]string{
|
||||
@@ -214,7 +217,7 @@ func (an *Client) SendStream(
|
||||
}
|
||||
|
||||
if stream.Err() != nil {
|
||||
fmt.Printf("Messages stream error: %v\n", stream.Err())
|
||||
fmt.Fprintf(os.Stderr, "Messages stream error: %v\n", stream.Err())
|
||||
}
|
||||
close(channel)
|
||||
return
|
||||
|
||||
@@ -2,7 +2,9 @@ package ollama
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -10,11 +12,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
ollamaapi "github.com/ollama/ollama/api"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/plugins"
|
||||
ollamaapi "github.com/ollama/ollama/api"
|
||||
)
|
||||
|
||||
const defaultBaseUrl = "http://localhost:11434"
|
||||
@@ -48,6 +49,7 @@ type Client struct {
|
||||
apiUrl *url.URL
|
||||
client *ollamaapi.Client
|
||||
ApiHttpTimeout *plugins.SetupQuestion
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type transport_sec struct {
|
||||
@@ -84,7 +86,8 @@ func (o *Client) configure() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
o.client = ollamaapi.NewClient(o.apiUrl, &http.Client{Timeout: timeout, Transport: &transport_sec{underlyingTransport: http.DefaultTransport, ApiKey: o.ApiKey}})
|
||||
o.httpClient = &http.Client{Timeout: timeout, Transport: &transport_sec{underlyingTransport: http.DefaultTransport, ApiKey: o.ApiKey}}
|
||||
o.client = ollamaapi.NewClient(o.apiUrl, o.httpClient)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -104,15 +107,18 @@ func (o *Client) ListModels() (ret []string, err error) {
|
||||
}
|
||||
|
||||
func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) (err error) {
|
||||
req := o.createChatRequest(msgs, opts)
|
||||
ctx := context.Background()
|
||||
|
||||
var req ollamaapi.ChatRequest
|
||||
if req, err = o.createChatRequest(ctx, msgs, opts); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
respFunc := func(resp ollamaapi.ChatResponse) (streamErr error) {
|
||||
channel <- resp.Message.Content
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if err = o.client.Chat(ctx, &req, respFunc); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -124,7 +130,10 @@ func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
|
||||
func (o *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (ret string, err error) {
|
||||
bf := false
|
||||
|
||||
req := o.createChatRequest(msgs, opts)
|
||||
var req ollamaapi.ChatRequest
|
||||
if req, err = o.createChatRequest(ctx, msgs, opts); err != nil {
|
||||
return
|
||||
}
|
||||
req.Stream = &bf
|
||||
|
||||
respFunc := func(resp ollamaapi.ChatResponse) (streamErr error) {
|
||||
@@ -133,15 +142,18 @@ func (o *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
|
||||
}
|
||||
|
||||
if err = o.client.Chat(ctx, &req, respFunc); err != nil {
|
||||
fmt.Printf("FRED --> %s\n", err)
|
||||
debuglog.Debug(debuglog.Basic, "Ollama chat request failed: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) createChatRequest(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (ret ollamaapi.ChatRequest) {
|
||||
messages := lo.Map(msgs, func(message *chat.ChatCompletionMessage, _ int) (ret ollamaapi.Message) {
|
||||
return ollamaapi.Message{Role: message.Role, Content: message.Content}
|
||||
})
|
||||
func (o *Client) createChatRequest(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (ret ollamaapi.ChatRequest, err error) {
|
||||
messages := make([]ollamaapi.Message, len(msgs))
|
||||
for i, message := range msgs {
|
||||
if messages[i], err = o.convertMessage(ctx, message); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
options := map[string]interface{}{
|
||||
"temperature": opts.Temperature,
|
||||
@@ -162,6 +174,77 @@ func (o *Client) createChatRequest(msgs []*chat.ChatCompletionMessage, opts *dom
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) convertMessage(ctx context.Context, message *chat.ChatCompletionMessage) (ret ollamaapi.Message, err error) {
|
||||
ret = ollamaapi.Message{Role: message.Role, Content: message.Content}
|
||||
|
||||
if len(message.MultiContent) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Pre-allocate with capacity hint
|
||||
textParts := make([]string, 0, len(message.MultiContent))
|
||||
if strings.TrimSpace(ret.Content) != "" {
|
||||
textParts = append(textParts, strings.TrimSpace(ret.Content))
|
||||
}
|
||||
|
||||
for _, part := range message.MultiContent {
|
||||
switch part.Type {
|
||||
case chat.ChatMessagePartTypeText:
|
||||
if trimmed := strings.TrimSpace(part.Text); trimmed != "" {
|
||||
textParts = append(textParts, trimmed)
|
||||
}
|
||||
case chat.ChatMessagePartTypeImageURL:
|
||||
// Nil guard
|
||||
if part.ImageURL == nil || part.ImageURL.URL == "" {
|
||||
continue
|
||||
}
|
||||
var img []byte
|
||||
if img, err = o.loadImageBytes(ctx, part.ImageURL.URL); err != nil {
|
||||
return
|
||||
}
|
||||
ret.Images = append(ret.Images, ollamaapi.ImageData(img))
|
||||
}
|
||||
}
|
||||
|
||||
ret.Content = strings.Join(textParts, "\n")
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) loadImageBytes(ctx context.Context, imageURL string) (ret []byte, err error) {
|
||||
// Handle data URLs (base64 encoded)
|
||||
if strings.HasPrefix(imageURL, "data:") {
|
||||
parts := strings.SplitN(imageURL, ",", 2)
|
||||
if len(parts) != 2 {
|
||||
err = fmt.Errorf("invalid data URL format")
|
||||
return
|
||||
}
|
||||
if ret, err = base64.StdEncoding.DecodeString(parts[1]); err != nil {
|
||||
err = fmt.Errorf("failed to decode data URL: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle HTTP URLs with context
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequestWithContext(ctx, http.MethodGet, imageURL, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
if resp, err = o.httpClient.Do(req); err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
err = fmt.Errorf("failed to fetch image %s: %s", imageURL, resp.Status)
|
||||
return
|
||||
}
|
||||
|
||||
ret, err = io.ReadAll(resp.Body)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) NeedsRawMode(modelName string) bool {
|
||||
ollamaSearchStrings := []string{
|
||||
"llama3",
|
||||
|
||||
@@ -2,6 +2,7 @@ package openai_compatible
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -38,8 +39,12 @@ func NewClient(providerConfig ProviderConfig) *Client {
|
||||
|
||||
// ListModels overrides the default ListModels to handle different response formats
|
||||
func (c *Client) ListModels() ([]string, error) {
|
||||
// If a custom models URL is provided, use direct fetch with that URL
|
||||
// If a custom models URL is provided, handle it
|
||||
if c.modelsURL != "" {
|
||||
// Check for static model list
|
||||
if strings.HasPrefix(c.modelsURL, "static:") {
|
||||
return c.getStaticModels(c.modelsURL)
|
||||
}
|
||||
// TODO: Handle context properly in Fabric by accepting and propagating a context.Context
|
||||
// instead of creating a new one here.
|
||||
return openai.FetchModelsDirectly(context.Background(), c.modelsURL, c.Client.ApiKey.Value, c.GetName())
|
||||
@@ -55,6 +60,68 @@ func (c *Client) ListModels() ([]string, error) {
|
||||
return c.DirectlyGetModels(context.Background())
|
||||
}
|
||||
|
||||
// getStaticModels returns a predefined list of models for providers that don't support model discovery
|
||||
func (c *Client) getStaticModels(modelsKey string) ([]string, error) {
|
||||
switch modelsKey {
|
||||
case "static:abacus":
|
||||
return []string{
|
||||
"route-llm",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-mini",
|
||||
"o4-mini",
|
||||
"o3-pro",
|
||||
"o3",
|
||||
"o3-mini",
|
||||
"gpt-4.1",
|
||||
"gpt-4.1-mini",
|
||||
"gpt-4.1-nano",
|
||||
"gpt-5",
|
||||
"gpt-5-mini",
|
||||
"gpt-5-nano",
|
||||
"gpt-5.1",
|
||||
"gpt-5.1-chat-latest",
|
||||
"openai/gpt-oss-120b",
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-opus-4-20250514",
|
||||
"claude-opus-4-1-20250805",
|
||||
"claude-sonnet-4-5-20250929",
|
||||
"claude-haiku-4-5-20251001",
|
||||
"claude-opus-4-5-20251101",
|
||||
"meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
|
||||
"meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo",
|
||||
"meta-llama/Meta-Llama-3.1-70B-Instruct",
|
||||
"meta-llama/Meta-Llama-3.1-8B-Instruct",
|
||||
"llama-3.3-70b-versatile",
|
||||
"gemini-2.0-flash-001",
|
||||
"gemini-2.0-pro-exp-02-05",
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.5-flash",
|
||||
"gemini-3-pro-preview",
|
||||
"qwen-2.5-coder-32b",
|
||||
"Qwen/Qwen2.5-72B-Instruct",
|
||||
"Qwen/QwQ-32B",
|
||||
"Qwen/Qwen3-235B-A22B-Instruct-2507",
|
||||
"Qwen/Qwen3-32B",
|
||||
"qwen/qwen3-coder-480b-a35b-instruct",
|
||||
"qwen/qwen3-Max",
|
||||
"grok-4-0709",
|
||||
"grok-4-fast-non-reasoning",
|
||||
"grok-4-1-fast-non-reasoning",
|
||||
"grok-code-fast-1",
|
||||
"kimi-k2-turbo-preview",
|
||||
"deepseek/deepseek-v3.1",
|
||||
"deepseek-ai/DeepSeek-V3.1-Terminus",
|
||||
"deepseek-ai/DeepSeek-R1",
|
||||
"deepseek-ai/DeepSeek-V3.2",
|
||||
"zai-org/glm-4.5",
|
||||
"zai-org/glm-4.6",
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown static model list: %s", modelsKey)
|
||||
}
|
||||
}
|
||||
|
||||
// ProviderMap is a map of provider name to ProviderConfig for O(1) lookup
|
||||
var ProviderMap = map[string]ProviderConfig{
|
||||
"AIML": {
|
||||
@@ -128,6 +195,12 @@ var ProviderMap = map[string]ProviderConfig{
|
||||
BaseURL: "https://api.z.ai/api/paas/v4",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"Abacus": {
|
||||
Name: "Abacus",
|
||||
BaseURL: "https://routellm.abacus.ai/v1/",
|
||||
ModelsURL: "static:abacus", // Special marker for static model list
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
}
|
||||
|
||||
// GetProviderByName returns the provider configuration for a given name with O(1) lookup
|
||||
|
||||
@@ -25,6 +25,11 @@ func TestCreateClient(t *testing.T) {
|
||||
provider: "Z AI",
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: "Existing provider - Abacus",
|
||||
provider: "Abacus",
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
name: "Non-existent provider",
|
||||
provider: "NonExistent",
|
||||
|
||||
@@ -92,7 +92,11 @@ func (o *PluginBase) Setup() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = o.Configure()
|
||||
// After Setup, run ConfigureCustom if present, but skip re-validation
|
||||
// since Ask() already validated user input (or allowed explicit reset)
|
||||
if o.ConfigureCustom != nil {
|
||||
err = o.ConfigureCustom()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -198,16 +202,21 @@ func (o *SetupQuestion) Ask(label string) (err error) {
|
||||
var answer string
|
||||
fmt.Scanln(&answer)
|
||||
answer = strings.TrimRight(answer, "\n")
|
||||
isReset := strings.ToLower(answer) == AnswerReset
|
||||
if answer == "" {
|
||||
answer = o.Value
|
||||
} else if strings.ToLower(answer) == AnswerReset {
|
||||
} else if isReset {
|
||||
answer = ""
|
||||
}
|
||||
err = o.OnAnswer(answer)
|
||||
err = o.OnAnswerWithReset(answer, isReset)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *SetupQuestion) OnAnswer(answer string) (err error) {
|
||||
return o.OnAnswerWithReset(answer, false)
|
||||
}
|
||||
|
||||
func (o *SetupQuestion) OnAnswerWithReset(answer string, isReset bool) (err error) {
|
||||
if o.Type == SettingTypeBool {
|
||||
if answer == "" {
|
||||
o.Value = ""
|
||||
@@ -226,6 +235,11 @@ func (o *SetupQuestion) OnAnswer(answer string) (err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Skip validation when explicitly resetting a value - the user intentionally
|
||||
// wants to clear the value even if it's required
|
||||
if isReset {
|
||||
return nil
|
||||
}
|
||||
err = o.IsValidErr()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,6 +116,91 @@ func TestSetupQuestion_Ask(t *testing.T) {
|
||||
assert.Equal(t, "user_value", setting.Value)
|
||||
}
|
||||
|
||||
func TestSetupQuestion_Ask_Reset(t *testing.T) {
|
||||
// Test that resetting a required field doesn't produce an error
|
||||
setting := &Setting{
|
||||
EnvVariable: "TEST_RESET_SETTING",
|
||||
Value: "existing_value",
|
||||
Required: true,
|
||||
}
|
||||
question := &SetupQuestion{
|
||||
Setting: setting,
|
||||
Question: "Enter test setting:",
|
||||
}
|
||||
input := "reset\n"
|
||||
fmtInput := captureInput(input)
|
||||
defer fmtInput()
|
||||
err := question.Ask("TestConfigurable")
|
||||
// Should NOT return an error even though the field is required
|
||||
assert.NoError(t, err)
|
||||
// Value should be cleared
|
||||
assert.Equal(t, "", setting.Value)
|
||||
}
|
||||
|
||||
func TestSetupQuestion_OnAnswerWithReset(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setting *Setting
|
||||
answer string
|
||||
isReset bool
|
||||
expectError bool
|
||||
expectValue string
|
||||
}{
|
||||
{
|
||||
name: "reset required field should not error",
|
||||
setting: &Setting{
|
||||
EnvVariable: "TEST_SETTING",
|
||||
Value: "old_value",
|
||||
Required: true,
|
||||
},
|
||||
answer: "",
|
||||
isReset: true,
|
||||
expectError: false,
|
||||
expectValue: "",
|
||||
},
|
||||
{
|
||||
name: "empty answer on required field should error",
|
||||
setting: &Setting{
|
||||
EnvVariable: "TEST_SETTING",
|
||||
Value: "",
|
||||
Required: true,
|
||||
},
|
||||
answer: "",
|
||||
isReset: false,
|
||||
expectError: true,
|
||||
expectValue: "",
|
||||
},
|
||||
{
|
||||
name: "valid answer on required field should not error",
|
||||
setting: &Setting{
|
||||
EnvVariable: "TEST_SETTING",
|
||||
Value: "",
|
||||
Required: true,
|
||||
},
|
||||
answer: "new_value",
|
||||
isReset: false,
|
||||
expectError: false,
|
||||
expectValue: "new_value",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
question := &SetupQuestion{
|
||||
Setting: tt.setting,
|
||||
Question: "Test question",
|
||||
}
|
||||
err := question.OnAnswerWithReset(tt.answer, tt.isReset)
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.expectValue, tt.setting.Value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettings_IsConfigured(t *testing.T) {
|
||||
settings := Settings{
|
||||
{EnvVariable: "TEST_SETTING1", Value: "value1", Required: true},
|
||||
|
||||
@@ -1 +1 @@
|
||||
"1.4.337"
|
||||
"1.4.342"
|
||||
|
||||
Reference in New Issue
Block a user