Add full macro-in-macro support so any user defined macro can contain another one as long as it was previously declared in the configuration file. Fixes #336 Supercedes #335
764 lines
18 KiB
Go
764 lines
18 KiB
Go
package config
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestConfig_GroupMemberIsUnique(t *testing.T) {
|
|
content := `
|
|
models:
|
|
model1:
|
|
cmd: path/to/cmd --arg1 one
|
|
proxy: "http://localhost:8080"
|
|
model2:
|
|
cmd: path/to/cmd --arg1 one
|
|
proxy: "http://localhost:8081"
|
|
checkEndpoint: "/"
|
|
model3:
|
|
cmd: path/to/cmd --arg1 one
|
|
proxy: "http://localhost:8081"
|
|
checkEndpoint: "/"
|
|
|
|
healthCheckTimeout: 15
|
|
groups:
|
|
group1:
|
|
swap: true
|
|
exclusive: false
|
|
members: ["model2"]
|
|
group2:
|
|
swap: true
|
|
exclusive: false
|
|
members: ["model2"]
|
|
`
|
|
// Load the config and verify
|
|
_, err := LoadConfigFromReader(strings.NewReader(content))
|
|
|
|
// a Contains as order of the map is not guaranteed
|
|
assert.Contains(t, err.Error(), "model member model2 is used in multiple groups:")
|
|
}
|
|
|
|
func TestConfig_ModelAliasesAreUnique(t *testing.T) {
|
|
content := `
|
|
models:
|
|
model1:
|
|
cmd: path/to/cmd --arg1 one
|
|
proxy: "http://localhost:8080"
|
|
aliases:
|
|
- m1
|
|
model2:
|
|
cmd: path/to/cmd --arg1 one
|
|
proxy: "http://localhost:8081"
|
|
checkEndpoint: "/"
|
|
aliases:
|
|
- m1
|
|
- m2
|
|
`
|
|
// Load the config and verify
|
|
_, err := LoadConfigFromReader(strings.NewReader(content))
|
|
|
|
// this is a contains because it could be `model1` or `model2` depending on the order
|
|
// go decided on the order of the map
|
|
assert.Contains(t, err.Error(), "duplicate alias m1 found in model: model")
|
|
}
|
|
|
|
func TestConfig_FindConfig(t *testing.T) {
|
|
|
|
// TODO?
|
|
// make make this shared between the different tests
|
|
config := &Config{
|
|
Models: map[string]ModelConfig{
|
|
"model1": {
|
|
Cmd: "python model1.py",
|
|
Proxy: "http://localhost:8080",
|
|
Aliases: []string{"m1", "model-one"},
|
|
Env: []string{"VAR1=value1", "VAR2=value2"},
|
|
CheckEndpoint: "/health",
|
|
},
|
|
"model2": {
|
|
Cmd: "python model2.py",
|
|
Proxy: "http://localhost:8081",
|
|
Aliases: []string{"m2", "model-two"},
|
|
Env: []string{"VAR3=value3", "VAR4=value4"},
|
|
CheckEndpoint: "/status",
|
|
},
|
|
},
|
|
HealthCheckTimeout: 10,
|
|
aliases: map[string]string{
|
|
"m1": "model1",
|
|
"model-one": "model1",
|
|
"m2": "model2",
|
|
},
|
|
}
|
|
|
|
// Test finding a model by its name
|
|
modelConfig, modelId, found := config.FindConfig("model1")
|
|
assert.True(t, found)
|
|
assert.Equal(t, "model1", modelId)
|
|
assert.Equal(t, config.Models["model1"], modelConfig)
|
|
|
|
// Test finding a model by its alias
|
|
modelConfig, modelId, found = config.FindConfig("m1")
|
|
assert.True(t, found)
|
|
assert.Equal(t, "model1", modelId)
|
|
assert.Equal(t, config.Models["model1"], modelConfig)
|
|
|
|
// Test finding a model that does not exist
|
|
modelConfig, modelId, found = config.FindConfig("model3")
|
|
assert.False(t, found)
|
|
assert.Equal(t, "", modelId)
|
|
assert.Equal(t, ModelConfig{}, modelConfig)
|
|
}
|
|
|
|
func TestConfig_AutomaticPortAssignments(t *testing.T) {
|
|
|
|
t.Run("Default Port Ranges", func(t *testing.T) {
|
|
content := ``
|
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
|
if !assert.NoError(t, err) {
|
|
t.Fatalf("Failed to load config: %v", err)
|
|
}
|
|
|
|
assert.Equal(t, 5800, config.StartPort)
|
|
})
|
|
t.Run("User specific port ranges", func(t *testing.T) {
|
|
content := `startPort: 1000`
|
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
|
if !assert.NoError(t, err) {
|
|
t.Fatalf("Failed to load config: %v", err)
|
|
}
|
|
|
|
assert.Equal(t, 1000, config.StartPort)
|
|
})
|
|
|
|
t.Run("Invalid start port", func(t *testing.T) {
|
|
content := `startPort: abcd`
|
|
_, err := LoadConfigFromReader(strings.NewReader(content))
|
|
assert.NotNil(t, err)
|
|
})
|
|
|
|
t.Run("start port must be greater than 1", func(t *testing.T) {
|
|
content := `startPort: -99`
|
|
_, err := LoadConfigFromReader(strings.NewReader(content))
|
|
assert.NotNil(t, err)
|
|
})
|
|
|
|
t.Run("Automatic port assignments", func(t *testing.T) {
|
|
content := `
|
|
startPort: 5800
|
|
models:
|
|
model1:
|
|
cmd: svr --port ${PORT}
|
|
model2:
|
|
cmd: svr --port ${PORT}
|
|
proxy: "http://172.11.22.33:${PORT}"
|
|
model3:
|
|
cmd: svr --port 1999
|
|
proxy: "http://1.2.3.4:1999"
|
|
`
|
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
|
if !assert.NoError(t, err) {
|
|
t.Fatalf("Failed to load config: %v", err)
|
|
}
|
|
|
|
assert.Equal(t, 5800, config.StartPort)
|
|
assert.Equal(t, "svr --port 5800", config.Models["model1"].Cmd)
|
|
assert.Equal(t, "http://localhost:5800", config.Models["model1"].Proxy)
|
|
|
|
assert.Equal(t, "svr --port 5801", config.Models["model2"].Cmd)
|
|
assert.Equal(t, "http://172.11.22.33:5801", config.Models["model2"].Proxy)
|
|
|
|
assert.Equal(t, "svr --port 1999", config.Models["model3"].Cmd)
|
|
assert.Equal(t, "http://1.2.3.4:1999", config.Models["model3"].Proxy)
|
|
|
|
})
|
|
|
|
t.Run("Proxy value required if no ${PORT} in cmd", func(t *testing.T) {
|
|
content := `
|
|
models:
|
|
model1:
|
|
cmd: svr --port 111
|
|
`
|
|
_, err := LoadConfigFromReader(strings.NewReader(content))
|
|
assert.Equal(t, "model model1: proxy uses ${PORT} but cmd does not - ${PORT} is only available when used in cmd", err.Error())
|
|
})
|
|
}
|
|
|
|
func TestConfig_MacroReplacement(t *testing.T) {
|
|
content := `
|
|
startPort: 9990
|
|
macros:
|
|
svr-path: "path/to/server"
|
|
argOne: "--arg1"
|
|
argTwo: "--arg2"
|
|
autoPort: "--port ${PORT}"
|
|
overriddenByModelMacro: failed
|
|
|
|
models:
|
|
model1:
|
|
macros:
|
|
overriddenByModelMacro: success
|
|
cmd: |
|
|
${svr-path} ${argTwo}
|
|
# the automatic ${PORT} is replaced
|
|
${autoPort}
|
|
${argOne}
|
|
--arg3 three
|
|
--overridden ${overriddenByModelMacro}
|
|
cmdStop: |
|
|
/path/to/stop.sh --port ${PORT} ${argTwo}
|
|
`
|
|
|
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
sanitizedCmd, err := SanitizeCommand(config.Models["model1"].Cmd)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "path/to/server --arg2 --port 9990 --arg1 --arg3 three --overridden success", strings.Join(sanitizedCmd, " "))
|
|
|
|
sanitizedCmdStop, err := SanitizeCommand(config.Models["model1"].CmdStop)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "/path/to/stop.sh --port 9990 --arg2", strings.Join(sanitizedCmdStop, " "))
|
|
}
|
|
|
|
func TestConfig_MacroReservedNames(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
name string
|
|
config string
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "global macro named PORT",
|
|
config: `
|
|
macros:
|
|
PORT: "1111"
|
|
`,
|
|
expectedError: "macro name 'PORT' is reserved",
|
|
},
|
|
{
|
|
name: "global macro named MODEL_ID",
|
|
config: `
|
|
macros:
|
|
MODEL_ID: model1
|
|
`,
|
|
expectedError: "macro name 'MODEL_ID' is reserved",
|
|
},
|
|
{
|
|
name: "model macro named PORT",
|
|
config: `
|
|
models:
|
|
model1:
|
|
macros:
|
|
PORT: 1111
|
|
`,
|
|
expectedError: "model model1: macro name 'PORT' is reserved",
|
|
},
|
|
|
|
{
|
|
name: "model macro named MODEL_ID",
|
|
config: `
|
|
models:
|
|
model1:
|
|
macros:
|
|
MODEL_ID: model1
|
|
`,
|
|
expectedError: "model model1: macro name 'MODEL_ID' is reserved",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := LoadConfigFromReader(strings.NewReader(tt.config))
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, tt.expectedError, err.Error())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConfig_MacroErrorOnUnknownMacros(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
field string
|
|
content string
|
|
}{
|
|
{
|
|
name: "unknown macro in cmd",
|
|
field: "cmd",
|
|
content: `
|
|
startPort: 9990
|
|
macros:
|
|
svr-path: "path/to/server"
|
|
models:
|
|
model1:
|
|
cmd: |
|
|
${svr-path} --port ${PORT}
|
|
${unknownMacro}
|
|
`,
|
|
},
|
|
{
|
|
name: "unknown macro in cmdStop",
|
|
field: "cmdStop",
|
|
content: `
|
|
startPort: 9990
|
|
macros:
|
|
svr-path: "path/to/server"
|
|
models:
|
|
model1:
|
|
cmd: "${svr-path} --port ${PORT}"
|
|
cmdStop: "kill ${unknownMacro}"
|
|
`,
|
|
},
|
|
{
|
|
name: "unknown macro in proxy",
|
|
field: "proxy",
|
|
content: `
|
|
startPort: 9990
|
|
macros:
|
|
svr-path: "path/to/server"
|
|
models:
|
|
model1:
|
|
cmd: "${svr-path} --port ${PORT}"
|
|
proxy: "http://${unknownMacro}:${PORT}"
|
|
`,
|
|
},
|
|
{
|
|
name: "unknown macro in checkEndpoint",
|
|
field: "checkEndpoint",
|
|
content: `
|
|
startPort: 9990
|
|
macros:
|
|
svr-path: "path/to/server"
|
|
models:
|
|
model1:
|
|
cmd: "${svr-path} --port ${PORT}"
|
|
checkEndpoint: "http://localhost:${unknownMacro}/health"
|
|
`,
|
|
},
|
|
{
|
|
name: "unknown macro in filters.stripParams",
|
|
field: "filters.stripParams",
|
|
content: `
|
|
startPort: 9990
|
|
macros:
|
|
svr-path: "path/to/server"
|
|
models:
|
|
model1:
|
|
cmd: "${svr-path} --port ${PORT}"
|
|
filters:
|
|
stripParams: "model,${unknownMacro}"
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := LoadConfigFromReader(strings.NewReader(tt.content))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "unknown macro '${unknownMacro}' found in model1."+tt.field)
|
|
}
|
|
//t.Log(err)
|
|
})
|
|
}
|
|
}
|
|
func TestStripComments(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "no comments",
|
|
input: "echo hello\necho world",
|
|
expected: "echo hello\necho world",
|
|
},
|
|
{
|
|
name: "single comment line",
|
|
input: "# this is a comment\necho hello",
|
|
expected: "echo hello",
|
|
},
|
|
{
|
|
name: "multiple comment lines",
|
|
input: "# comment 1\necho hello\n# comment 2\necho world",
|
|
expected: "echo hello\necho world",
|
|
},
|
|
{
|
|
name: "comment with spaces",
|
|
input: " # indented comment\necho hello",
|
|
expected: "echo hello",
|
|
},
|
|
{
|
|
name: "empty lines preserved",
|
|
input: "echo hello\n\necho world",
|
|
expected: "echo hello\n\necho world",
|
|
},
|
|
{
|
|
name: "only comments",
|
|
input: "# comment 1\n# comment 2",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "empty string",
|
|
input: "",
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := StripComments(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("StripComments() = %q, expected %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConfig_MacroInCommentStrippedBeforeExpansion(t *testing.T) {
|
|
// Test case that reproduces the original bug where a macro in a comment
|
|
// would get expanded and cause the comment text to be included in the command
|
|
content := `
|
|
startPort: 9990
|
|
macros:
|
|
"latest-llama": >
|
|
/user/llama.cpp/build/bin/llama-server
|
|
--port ${PORT}
|
|
|
|
models:
|
|
"test-model":
|
|
cmd: |
|
|
# ${latest-llama} is a macro that is defined above
|
|
${latest-llama}
|
|
--model /path/to/model.gguf
|
|
-ngl 99
|
|
`
|
|
|
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
|
assert.NoError(t, err)
|
|
|
|
// Get the sanitized command
|
|
sanitizedCmd, err := SanitizeCommand(config.Models["test-model"].Cmd)
|
|
assert.NoError(t, err)
|
|
|
|
// Join the command for easier inspection
|
|
cmdStr := strings.Join(sanitizedCmd, " ")
|
|
|
|
// Verify that comment text is NOT present in the final command as separate arguments
|
|
commentWords := []string{"is", "macro", "that", "defined", "above"}
|
|
for _, word := range commentWords {
|
|
found := slices.Contains(sanitizedCmd, word)
|
|
assert.False(t, found, "Comment text '%s' should not be present as a separate argument in final command", word)
|
|
}
|
|
|
|
// Verify that the actual command components ARE present
|
|
expectedParts := []string{
|
|
"/user/llama.cpp/build/bin/llama-server",
|
|
"--port",
|
|
"9990",
|
|
"--model",
|
|
"/path/to/model.gguf",
|
|
"-ngl",
|
|
"99",
|
|
}
|
|
|
|
for _, part := range expectedParts {
|
|
assert.Contains(t, cmdStr, part, "Expected command part '%s' not found in final command", part)
|
|
}
|
|
|
|
// Verify the server path appears exactly once (not duplicated due to macro expansion)
|
|
serverPath := "/user/llama.cpp/build/bin/llama-server"
|
|
count := strings.Count(cmdStr, serverPath)
|
|
assert.Equal(t, 1, count, "Expected exactly 1 occurrence of server path, found %d", count)
|
|
|
|
// Verify the expected final command structure
|
|
expectedCmd := "/user/llama.cpp/build/bin/llama-server --port 9990 --model /path/to/model.gguf -ngl 99"
|
|
assert.Equal(t, expectedCmd, cmdStr, "Final command does not match expected structure")
|
|
}
|
|
|
|
func TestConfig_MacroModelId(t *testing.T) {
|
|
content := `
|
|
startPort: 9000
|
|
macros:
|
|
"docker-llama": docker run --name ${MODEL_ID} -p ${PORT}:8080 docker_img
|
|
"docker-stop": docker stop ${MODEL_ID}
|
|
|
|
models:
|
|
model1:
|
|
cmd: /path/to/server -p ${PORT} -hf ${MODEL_ID}
|
|
|
|
model2:
|
|
cmd: ${docker-llama}
|
|
cmdStop: ${docker-stop}
|
|
|
|
author/model:F16:
|
|
cmd: /path/to/server -p ${PORT} -hf ${MODEL_ID}
|
|
cmdStop: stop
|
|
`
|
|
|
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
|
assert.NoError(t, err)
|
|
sanitizedCmd, err := SanitizeCommand(config.Models["model1"].Cmd)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "/path/to/server -p 9001 -hf model1", strings.Join(sanitizedCmd, " "))
|
|
|
|
dockerStopMacro, found := config.Macros.Get("docker-stop")
|
|
assert.True(t, found)
|
|
assert.Equal(t, "docker stop ${MODEL_ID}", dockerStopMacro)
|
|
|
|
sanitizedCmd2, err := SanitizeCommand(config.Models["model2"].Cmd)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "docker run --name model2 -p 9002:8080 docker_img", strings.Join(sanitizedCmd2, " "))
|
|
|
|
sanitizedCmdStop, err := SanitizeCommand(config.Models["model2"].CmdStop)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "docker stop model2", strings.Join(sanitizedCmdStop, " "))
|
|
|
|
sanitizedCmd3, err := SanitizeCommand(config.Models["author/model:F16"].Cmd)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "/path/to/server -p 9000 -hf author/model:F16", strings.Join(sanitizedCmd3, " "))
|
|
}
|
|
|
|
func TestConfig_TypedMacrosInMetadata(t *testing.T) {
|
|
content := `
|
|
startPort: 10000
|
|
macros:
|
|
PORT_NUM: 10001
|
|
TEMP: 0.7
|
|
ENABLED: true
|
|
NAME: "llama model"
|
|
CTX: 16384
|
|
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
metadata:
|
|
port: ${PORT_NUM}
|
|
temperature: ${TEMP}
|
|
enabled: ${ENABLED}
|
|
model_name: ${NAME}
|
|
context: ${CTX}
|
|
note: "Running on port ${PORT_NUM} with temp ${TEMP} and context ${CTX}"
|
|
`
|
|
|
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
|
assert.NoError(t, err)
|
|
|
|
meta := config.Models["test-model"].Metadata
|
|
assert.NotNil(t, meta)
|
|
|
|
// Verify direct substitution preserves types
|
|
assert.Equal(t, 10001, meta["port"])
|
|
assert.Equal(t, 0.7, meta["temperature"])
|
|
assert.Equal(t, true, meta["enabled"])
|
|
assert.Equal(t, "llama model", meta["model_name"])
|
|
assert.Equal(t, 16384, meta["context"])
|
|
|
|
// Verify string interpolation converts to string
|
|
assert.Equal(t, "Running on port 10001 with temp 0.7 and context 16384", meta["note"])
|
|
}
|
|
|
|
func TestConfig_NestedStructuresInMetadata(t *testing.T) {
|
|
content := `
|
|
startPort: 10000
|
|
macros:
|
|
TEMP: 0.7
|
|
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
metadata:
|
|
config:
|
|
port: ${PORT}
|
|
temperature: ${TEMP}
|
|
tags: ["model:${MODEL_ID}", "port:${PORT}"]
|
|
nested:
|
|
deep:
|
|
value: ${TEMP}
|
|
`
|
|
|
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
|
assert.NoError(t, err)
|
|
|
|
meta := config.Models["test-model"].Metadata
|
|
assert.NotNil(t, meta)
|
|
|
|
// Verify nested objects
|
|
configMap := meta["config"].(map[string]any)
|
|
assert.Equal(t, 10000, configMap["port"])
|
|
assert.Equal(t, 0.7, configMap["temperature"])
|
|
|
|
// Verify arrays
|
|
tags := meta["tags"].([]any)
|
|
assert.Equal(t, "model:test-model", tags[0])
|
|
assert.Equal(t, "port:10000", tags[1])
|
|
|
|
// Verify deeply nested structures
|
|
nested := meta["nested"].(map[string]any)
|
|
deep := nested["deep"].(map[string]any)
|
|
assert.Equal(t, 0.7, deep["value"])
|
|
}
|
|
|
|
func TestConfig_ModelLevelMacroPrecedenceInMetadata(t *testing.T) {
|
|
content := `
|
|
startPort: 10000
|
|
macros:
|
|
TEMP: 0.5
|
|
GLOBAL_VAL: "global"
|
|
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
macros:
|
|
TEMP: 0.9
|
|
LOCAL_VAL: "local"
|
|
metadata:
|
|
temperature: ${TEMP}
|
|
global: ${GLOBAL_VAL}
|
|
local: ${LOCAL_VAL}
|
|
`
|
|
|
|
config, err := LoadConfigFromReader(strings.NewReader(content))
|
|
assert.NoError(t, err)
|
|
|
|
meta := config.Models["test-model"].Metadata
|
|
assert.NotNil(t, meta)
|
|
|
|
// Model-level macro should override global
|
|
assert.Equal(t, 0.9, meta["temperature"])
|
|
// Global macro should be accessible
|
|
assert.Equal(t, "global", meta["global"])
|
|
// Model-level macro should be accessible
|
|
assert.Equal(t, "local", meta["local"])
|
|
}
|
|
|
|
func TestConfig_UnknownMacroInMetadata(t *testing.T) {
|
|
content := `
|
|
startPort: 10000
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
metadata:
|
|
value: ${UNKNOWN_MACRO}
|
|
`
|
|
|
|
_, err := LoadConfigFromReader(strings.NewReader(content))
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "test-model")
|
|
assert.Contains(t, err.Error(), "UNKNOWN_MACRO")
|
|
}
|
|
|
|
func TestConfig_InvalidMacroType(t *testing.T) {
|
|
content := `
|
|
startPort: 10000
|
|
macros:
|
|
INVALID:
|
|
nested: value
|
|
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
`
|
|
|
|
_, err := LoadConfigFromReader(strings.NewReader(content))
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "INVALID")
|
|
assert.Contains(t, err.Error(), "must be a scalar type")
|
|
}
|
|
|
|
func TestConfig_MacroTypeValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
yaml string
|
|
shouldErr bool
|
|
}{
|
|
{
|
|
name: "string macro",
|
|
yaml: `
|
|
startPort: 10000
|
|
macros:
|
|
STR: "test"
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
`,
|
|
shouldErr: false,
|
|
},
|
|
{
|
|
name: "int macro",
|
|
yaml: `
|
|
startPort: 10000
|
|
macros:
|
|
NUM: 42
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
`,
|
|
shouldErr: false,
|
|
},
|
|
{
|
|
name: "float macro",
|
|
yaml: `
|
|
startPort: 10000
|
|
macros:
|
|
FLOAT: 3.14
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
`,
|
|
shouldErr: false,
|
|
},
|
|
{
|
|
name: "bool macro",
|
|
yaml: `
|
|
startPort: 10000
|
|
macros:
|
|
BOOL: true
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
`,
|
|
shouldErr: false,
|
|
},
|
|
{
|
|
name: "array macro (invalid)",
|
|
yaml: `
|
|
startPort: 10000
|
|
macros:
|
|
ARR: [1, 2, 3]
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
`,
|
|
shouldErr: true,
|
|
},
|
|
{
|
|
name: "map macro (invalid)",
|
|
yaml: `
|
|
startPort: 10000
|
|
macros:
|
|
MAP:
|
|
key: value
|
|
models:
|
|
test-model:
|
|
cmd: /path/to/server -p ${PORT}
|
|
`,
|
|
shouldErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := LoadConfigFromReader(strings.NewReader(tt.yaml))
|
|
if tt.shouldErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|