- Marked the Cache Implementation as complete in TODO.md, confirming all tasks are finished with comprehensive integration tests. - Updated cache.go to provide a thread-safe in-memory cache for Robot instances, improving performance and reliability. - Enhanced the Load method to include pagination and configurable model name, ensuring efficient data handling. - Added detailed comments and structured code for better readability and maintainability across the cache implementation. - Improved validation and error handling in various utility functions to ensure robustness in data processing.
299 lines
7 KiB
Go
299 lines
7 KiB
Go
package utils_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/yaoapp/yao/agent/robot/utils"
|
|
)
|
|
|
|
// ID tests
|
|
func TestNewID(t *testing.T) {
|
|
id1 := utils.NewID()
|
|
id2 := utils.NewID()
|
|
|
|
assert.NotEmpty(t, id1)
|
|
assert.NotEmpty(t, id2)
|
|
assert.NotEqual(t, id1, id2, "IDs should be unique")
|
|
}
|
|
|
|
func TestNewIDWithPrefix(t *testing.T) {
|
|
id := utils.NewIDWithPrefix("exec_")
|
|
assert.NotEmpty(t, id)
|
|
assert.Contains(t, id, "exec_")
|
|
}
|
|
|
|
// Time tests
|
|
func TestParseTime(t *testing.T) {
|
|
t.Run("valid time", func(t *testing.T) {
|
|
hour, minute, err := utils.ParseTime("14:30")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 14, hour)
|
|
assert.Equal(t, 30, minute)
|
|
})
|
|
|
|
t.Run("invalid format", func(t *testing.T) {
|
|
_, _, err := utils.ParseTime("14-30")
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("invalid hour", func(t *testing.T) {
|
|
_, _, err := utils.ParseTime("25:30")
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("invalid minute", func(t *testing.T) {
|
|
_, _, err := utils.ParseTime("14:65")
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestFormatTime(t *testing.T) {
|
|
result := utils.FormatTime(9, 5)
|
|
assert.Equal(t, "09:05", result)
|
|
|
|
result = utils.FormatTime(14, 30)
|
|
assert.Equal(t, "14:30", result)
|
|
}
|
|
|
|
func TestLoadLocation(t *testing.T) {
|
|
t.Run("valid timezone", func(t *testing.T) {
|
|
loc := utils.LoadLocation("Asia/Shanghai")
|
|
assert.NotNil(t, loc)
|
|
assert.Equal(t, "Asia/Shanghai", loc.String())
|
|
})
|
|
|
|
t.Run("empty timezone returns Local", func(t *testing.T) {
|
|
loc := utils.LoadLocation("")
|
|
assert.Equal(t, time.Local, loc)
|
|
})
|
|
|
|
t.Run("invalid timezone returns Local", func(t *testing.T) {
|
|
loc := utils.LoadLocation("Invalid/Timezone")
|
|
assert.Equal(t, time.Local, loc)
|
|
})
|
|
}
|
|
|
|
func TestParseDuration(t *testing.T) {
|
|
t.Run("valid duration", func(t *testing.T) {
|
|
dur := utils.ParseDuration("30m", 10*time.Minute)
|
|
assert.Equal(t, 30*time.Minute, dur)
|
|
})
|
|
|
|
t.Run("empty returns default", func(t *testing.T) {
|
|
dur := utils.ParseDuration("", 10*time.Minute)
|
|
assert.Equal(t, 10*time.Minute, dur)
|
|
})
|
|
|
|
t.Run("invalid returns default", func(t *testing.T) {
|
|
dur := utils.ParseDuration("invalid", 10*time.Minute)
|
|
assert.Equal(t, 10*time.Minute, dur)
|
|
})
|
|
}
|
|
|
|
func TestIsTimeMatch(t *testing.T) {
|
|
loc := time.UTC
|
|
testTime := time.Date(2024, 1, 15, 14, 30, 0, 0, loc)
|
|
|
|
t.Run("exact match", func(t *testing.T) {
|
|
assert.True(t, utils.IsTimeMatch(testTime, "14:30", loc))
|
|
})
|
|
|
|
t.Run("no match", func(t *testing.T) {
|
|
assert.False(t, utils.IsTimeMatch(testTime, "14:31", loc))
|
|
assert.False(t, utils.IsTimeMatch(testTime, "15:30", loc))
|
|
})
|
|
|
|
t.Run("invalid time format", func(t *testing.T) {
|
|
assert.False(t, utils.IsTimeMatch(testTime, "invalid", loc))
|
|
})
|
|
}
|
|
|
|
func TestIsDayMatch(t *testing.T) {
|
|
monday := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC) // Monday
|
|
|
|
t.Run("match specific day", func(t *testing.T) {
|
|
assert.True(t, utils.IsDayMatch(monday, []string{"Mon"}))
|
|
})
|
|
|
|
t.Run("match wildcard", func(t *testing.T) {
|
|
assert.True(t, utils.IsDayMatch(monday, []string{"*"}))
|
|
})
|
|
|
|
t.Run("no match", func(t *testing.T) {
|
|
assert.False(t, utils.IsDayMatch(monday, []string{"Tue", "Wed"}))
|
|
})
|
|
|
|
t.Run("empty days returns true", func(t *testing.T) {
|
|
assert.True(t, utils.IsDayMatch(monday, []string{}))
|
|
})
|
|
}
|
|
|
|
// Convert tests
|
|
func TestToJSON(t *testing.T) {
|
|
data := map[string]interface{}{
|
|
"name": "test",
|
|
"age": 30,
|
|
}
|
|
|
|
json, err := utils.ToJSON(data)
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, json, "test")
|
|
assert.Contains(t, json, "30")
|
|
}
|
|
|
|
func TestFromJSON(t *testing.T) {
|
|
jsonStr := `{"name":"test","age":30}`
|
|
|
|
var result map[string]interface{}
|
|
err := utils.FromJSON(jsonStr, &result)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "test", result["name"])
|
|
assert.Equal(t, float64(30), result["age"]) // JSON numbers are float64
|
|
}
|
|
|
|
func TestToMap(t *testing.T) {
|
|
type TestStruct struct {
|
|
Name string `json:"name"`
|
|
Age int `json:"age"`
|
|
}
|
|
|
|
s := TestStruct{Name: "test", Age: 30}
|
|
m, err := utils.ToMap(s)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "test", m["name"])
|
|
assert.Equal(t, float64(30), m["age"]) // JSON conversion makes it float64
|
|
}
|
|
|
|
func TestFromMap(t *testing.T) {
|
|
type TestStruct struct {
|
|
Name string `json:"name"`
|
|
Age int `json:"age"`
|
|
}
|
|
|
|
m := map[string]interface{}{
|
|
"name": "test",
|
|
"age": 30,
|
|
}
|
|
|
|
var result TestStruct
|
|
err := utils.FromMap(m, &result)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "test", result.Name)
|
|
assert.Equal(t, 30, result.Age)
|
|
}
|
|
|
|
func TestToString(t *testing.T) {
|
|
assert.Equal(t, "test", utils.ToString("test"))
|
|
assert.Equal(t, "42", utils.ToString(42))
|
|
assert.Equal(t, "true", utils.ToString(true))
|
|
}
|
|
|
|
func TestMergeMap(t *testing.T) {
|
|
target := map[string]interface{}{
|
|
"a": 1,
|
|
"b": 2,
|
|
}
|
|
source := map[string]interface{}{
|
|
"b": 3,
|
|
"c": 4,
|
|
}
|
|
|
|
result := utils.MergeMap(target, source)
|
|
assert.Equal(t, 1, result["a"])
|
|
assert.Equal(t, 3, result["b"]) // overwritten
|
|
assert.Equal(t, 4, result["c"])
|
|
}
|
|
|
|
func TestCloneMap(t *testing.T) {
|
|
original := map[string]interface{}{
|
|
"a": 1,
|
|
"b": 2,
|
|
}
|
|
|
|
cloned := utils.CloneMap(original)
|
|
cloned["a"] = 999
|
|
|
|
assert.Equal(t, 1, original["a"]) // original unchanged
|
|
assert.Equal(t, 999, cloned["a"])
|
|
}
|
|
|
|
// Validate tests
|
|
func TestIsEmpty(t *testing.T) {
|
|
assert.True(t, utils.IsEmpty(""))
|
|
assert.False(t, utils.IsEmpty("test"))
|
|
}
|
|
|
|
func TestIsValidEmail(t *testing.T) {
|
|
assert.True(t, utils.IsValidEmail("test@example.com"))
|
|
assert.True(t, utils.IsValidEmail("user+tag@domain.co.uk"))
|
|
assert.False(t, utils.IsValidEmail("invalid"))
|
|
assert.False(t, utils.IsValidEmail("@example.com"))
|
|
assert.False(t, utils.IsValidEmail("test@"))
|
|
}
|
|
|
|
func TestIsValidTime(t *testing.T) {
|
|
assert.True(t, utils.IsValidTime("09:00"))
|
|
assert.True(t, utils.IsValidTime("14:30"))
|
|
assert.True(t, utils.IsValidTime("23:59"))
|
|
assert.False(t, utils.IsValidTime("25:00"))
|
|
assert.False(t, utils.IsValidTime("14:65"))
|
|
assert.False(t, utils.IsValidTime("14-30"))
|
|
}
|
|
|
|
func TestValidateRequired(t *testing.T) {
|
|
t.Run("nil value", func(t *testing.T) {
|
|
err := utils.ValidateRequired("field", nil)
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("empty string", func(t *testing.T) {
|
|
err := utils.ValidateRequired("field", "")
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("valid string", func(t *testing.T) {
|
|
err := utils.ValidateRequired("field", "value")
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("empty slice", func(t *testing.T) {
|
|
err := utils.ValidateRequired("field", []string{})
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestValidateRange(t *testing.T) {
|
|
t.Run("within range", func(t *testing.T) {
|
|
err := utils.ValidateRange("field", 5, 1, 10)
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("below range", func(t *testing.T) {
|
|
err := utils.ValidateRange("field", 0, 1, 10)
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("above range", func(t *testing.T) {
|
|
err := utils.ValidateRange("field", 11, 1, 10)
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestValidateOneOf(t *testing.T) {
|
|
allowed := []string{"apple", "banana", "cherry"}
|
|
|
|
t.Run("valid value", func(t *testing.T) {
|
|
err := utils.ValidateOneOf("field", "banana", allowed)
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("invalid value", func(t *testing.T) {
|
|
err := utils.ValidateOneOf("field", "orange", allowed)
|
|
assert.Error(t, err)
|
|
})
|
|
}
|