Files
gatus/config/gontext/gontext_test.go
TwiN d668a14703 feat(suite): Implement Suites (#1239)
* feat(suite): Implement Suites

Fixes #1230

* Update docs

* Fix variable alignment

* Prevent always-run endpoint from running if a context placeholder fails to resolve in the URL

* Return errors when a context placeholder path fails to resolve

* Add a couple of unit tests

* Add a couple of unit tests

* fix(ui): Update group count properly

Fixes #1233

* refactor: Pass down entire config instead of several sub-configs

* fix: Change default suite interval and timeout

* fix: Deprecate disable-monitoring-lock in favor of concurrency

* fix: Make sure there are no duplicate keys

* Refactor some code

* Update watchdog/watchdog.go

* Update web/app/src/components/StepDetailsModal.vue

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* chore: Remove useless log

* fix: Set default concurrency to 3 instead of 5

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-05 15:39:12 -04:00

449 lines
9.1 KiB
Go

package gontext
import (
"errors"
"testing"
)
func TestNew(t *testing.T) {
tests := []struct {
name string
initial map[string]interface{}
expected map[string]interface{}
}{
{
name: "nil-input",
initial: nil,
expected: make(map[string]interface{}),
},
{
name: "empty-input",
initial: make(map[string]interface{}),
expected: make(map[string]interface{}),
},
{
name: "simple-values",
initial: map[string]interface{}{
"key1": "value1",
"key2": 42,
},
expected: map[string]interface{}{
"key1": "value1",
"key2": 42,
},
},
{
name: "nested-values",
initial: map[string]interface{}{
"user": map[string]interface{}{
"id": 123,
"name": "John Doe",
},
},
expected: map[string]interface{}{
"user": map[string]interface{}{
"id": 123,
"name": "John Doe",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := New(tt.initial)
if ctx == nil {
t.Error("Expected non-nil gontext")
}
if ctx.values == nil {
t.Error("Expected non-nil values map")
}
// Verify deep copy by modifying original
if tt.initial != nil {
tt.initial["modified"] = "should not appear"
if _, exists := ctx.values["modified"]; exists {
t.Error("Deep copy failed - original map modification affected gontext")
}
}
})
}
}
func TestGontext_Get(t *testing.T) {
ctx := New(map[string]interface{}{
"simple": "value",
"number": 42,
"boolean": true,
"nested": map[string]interface{}{
"level1": map[string]interface{}{
"level2": "deep_value",
},
},
"user": map[string]interface{}{
"id": 123,
"name": "John",
"profile": map[string]interface{}{
"email": "john@example.com",
},
},
})
tests := []struct {
name string
path string
expected interface{}
shouldError bool
errorType error
}{
{
name: "simple-value",
path: "simple",
expected: "value",
shouldError: false,
},
{
name: "number-value",
path: "number",
expected: 42,
shouldError: false,
},
{
name: "boolean-value",
path: "boolean",
expected: true,
shouldError: false,
},
{
name: "nested-value",
path: "nested.level1.level2",
expected: "deep_value",
shouldError: false,
},
{
name: "user-id",
path: "user.id",
expected: 123,
shouldError: false,
},
{
name: "deep-nested-value",
path: "user.profile.email",
expected: "john@example.com",
shouldError: false,
},
{
name: "non-existent-key",
path: "nonexistent",
expected: nil,
shouldError: true,
errorType: ErrGontextPathNotFound,
},
{
name: "non-existent-nested-key",
path: "user.nonexistent",
expected: nil,
shouldError: true,
errorType: ErrGontextPathNotFound,
},
{
name: "invalid-nested-path",
path: "simple.invalid",
expected: nil,
shouldError: true,
errorType: ErrGontextPathNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ctx.Get(tt.path)
if tt.shouldError {
if err == nil {
t.Errorf("Expected error but got none")
}
if tt.errorType != nil && !errors.Is(err, tt.errorType) {
t.Errorf("Expected error type %v, got %v", tt.errorType, err)
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("Expected %v, got %v", tt.expected, result)
}
}
})
}
}
func TestGontext_Set(t *testing.T) {
tests := []struct {
name string
path string
value interface{}
wantErr bool
}{
{
name: "simple-set",
path: "key",
value: "value",
wantErr: false,
},
{
name: "nested-set",
path: "user.name",
value: "John Doe",
wantErr: false,
},
{
name: "deep-nested-set",
path: "user.profile.email",
value: "john@example.com",
wantErr: false,
},
{
name: "override-primitive-with-nested",
path: "existing.new",
value: "nested_value",
wantErr: false,
},
{
name: "empty-path",
path: "",
value: "value",
wantErr: false, // Actually, empty string creates a single part [""], which is valid
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := New(map[string]interface{}{
"existing": "primitive",
})
err := ctx.Set(tt.path, tt.value)
if tt.wantErr {
if err == nil {
t.Error("Expected error but got none")
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
// Verify the value was set correctly
result, getErr := ctx.Get(tt.path)
if getErr != nil {
t.Errorf("Error retrieving set value: %v", getErr)
return
}
if result != tt.value {
t.Errorf("Expected %v, got %v", tt.value, result)
}
})
}
}
func TestGontext_SetOverrideBehavior(t *testing.T) {
ctx := New(map[string]interface{}{
"primitive": "value",
"nested": map[string]interface{}{
"key": "existing",
},
})
// Test overriding primitive with nested structure
err := ctx.Set("primitive.new", "nested_value")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Verify the primitive was replaced with a nested structure
result, err := ctx.Get("primitive.new")
if err != nil {
t.Errorf("Error getting nested value: %v", err)
}
if result != "nested_value" {
t.Errorf("Expected 'nested_value', got %v", result)
}
// Test overriding existing nested value
err = ctx.Set("nested.key", "modified")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
result, err = ctx.Get("nested.key")
if err != nil {
t.Errorf("Error getting modified value: %v", err)
}
if result != "modified" {
t.Errorf("Expected 'modified', got %v", result)
}
}
func TestGontext_GetAll(t *testing.T) {
initial := map[string]interface{}{
"key1": "value1",
"key2": 42,
"nested": map[string]interface{}{
"inner": "value",
},
}
ctx := New(initial)
// Add another value after creation
ctx.Set("key3", "value3")
result := ctx.GetAll()
// Verify all values are present
if result["key1"] != "value1" {
t.Errorf("Expected key1=value1, got %v", result["key1"])
}
if result["key2"] != 42 {
t.Errorf("Expected key2=42, got %v", result["key2"])
}
if result["key3"] != "value3" {
t.Errorf("Expected key3=value3, got %v", result["key3"])
}
// Verify nested values
nested, ok := result["nested"].(map[string]interface{})
if !ok {
t.Error("Expected nested to be map[string]interface{}")
} else if nested["inner"] != "value" {
t.Errorf("Expected nested.inner=value, got %v", nested["inner"])
}
// Verify deep copy - modifying returned map shouldn't affect gontext
result["key1"] = "modified"
original, _ := ctx.Get("key1")
if original != "value1" {
t.Error("GetAll did not return a deep copy - modification affected original")
}
}
func TestGontext_ConcurrentAccess(t *testing.T) {
ctx := New(map[string]interface{}{
"counter": 0,
})
done := make(chan bool, 10)
// Start 5 goroutines that read values
for i := 0; i < 5; i++ {
go func(id int) {
for j := 0; j < 100; j++ {
_, err := ctx.Get("counter")
if err != nil {
t.Errorf("Reader %d error: %v", id, err)
}
}
done <- true
}(i)
}
// Start 5 goroutines that write values
for i := 0; i < 5; i++ {
go func(id int) {
for j := 0; j < 100; j++ {
err := ctx.Set("counter", id*1000+j)
if err != nil {
t.Errorf("Writer %d error: %v", id, err)
}
}
done <- true
}(i)
}
// Wait for all goroutines to complete
for i := 0; i < 10; i++ {
<-done
}
}
func TestDeepCopyValue(t *testing.T) {
tests := []struct {
name string
input interface{}
}{
{
name: "primitive-string",
input: "test",
},
{
name: "primitive-int",
input: 42,
},
{
name: "primitive-bool",
input: true,
},
{
name: "simple-map",
input: map[string]interface{}{
"key": "value",
},
},
{
name: "nested-map",
input: map[string]interface{}{
"nested": map[string]interface{}{
"deep": "value",
},
},
},
{
name: "simple-slice",
input: []interface{}{"a", "b", "c"},
},
{
name: "mixed-slice",
input: []interface{}{
"string",
42,
map[string]interface{}{"nested": "value"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := deepCopyValue(tt.input)
// For maps and slices, verify it's a different object
switch v := tt.input.(type) {
case map[string]interface{}:
resultMap, ok := result.(map[string]interface{})
if !ok {
t.Error("Deep copy didn't preserve map type")
return
}
// Modify original to ensure independence
v["modified"] = "test"
if _, exists := resultMap["modified"]; exists {
t.Error("Deep copy failed - maps are not independent")
}
case []interface{}:
resultSlice, ok := result.([]interface{})
if !ok {
t.Error("Deep copy didn't preserve slice type")
return
}
if len(resultSlice) != len(v) {
t.Error("Deep copy didn't preserve slice length")
}
}
})
}
}