Files
2026-07-13 12:49:10 +08:00

1129 lines
32 KiB
Go

/*
* Copyright 2025 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package core
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
// Define AddressSegmentType constants locally to avoid dependency cycles
const (
AddressSegmentAgent AddressSegmentType = "agent"
AddressSegmentTool AddressSegmentType = "tool"
AddressSegmentNode AddressSegmentType = "node"
)
func TestInterruptConversion(t *testing.T) {
// Test Case 1: Simple Chain (A -> B -> C)
t.Run("SimpleChain", func(t *testing.T) {
// Manually construct the user-facing contexts with parent pointers
ctxA := &InterruptCtx{ID: "A", IsRootCause: false}
ctxB := &InterruptCtx{ID: "B", Parent: ctxA, IsRootCause: false}
ctxC := &InterruptCtx{ID: "C", Parent: ctxB, IsRootCause: true}
// The input to FromInterruptContexts is just the root cause leaf node
contexts := []*InterruptCtx{ctxC}
// Convert from user-facing contexts to internal signal tree
signal := FromInterruptContexts(contexts)
// Assertions for the signal tree structure
assert.NotNil(t, signal)
assert.Equal(t, "A", signal.ID)
assert.Len(t, signal.Subs, 1)
assert.Equal(t, "B", signal.Subs[0].ID)
assert.Len(t, signal.Subs[0].Subs, 1)
assert.Equal(t, "C", signal.Subs[0].Subs[0].ID)
assert.True(t, signal.Subs[0].Subs[0].IsRootCause)
// Convert back from the signal tree to user-facing contexts
finalContexts := ToInterruptContexts(signal, nil)
// Assertions for the final list of contexts
assert.Len(t, finalContexts, 1)
finalC := finalContexts[0]
assert.Equal(t, "C", finalC.ID)
assert.True(t, finalC.IsRootCause)
assert.NotNil(t, finalC.Parent)
assert.Equal(t, "B", finalC.Parent.ID)
assert.NotNil(t, finalC.Parent.Parent)
assert.Equal(t, "A", finalC.Parent.Parent.ID)
assert.Nil(t, finalC.Parent.Parent.Parent)
})
// Test Case 2: Multiple Root Causes with Shared Parent (B -> D, C -> D)
t.Run("MultipleRootsSharedParent", func(t *testing.T) {
// Manually construct the contexts
ctxD := &InterruptCtx{ID: "D", IsRootCause: false}
ctxB := &InterruptCtx{ID: "B", Parent: ctxD, IsRootCause: true}
ctxC := &InterruptCtx{ID: "C", Parent: ctxD, IsRootCause: true}
// The input contains both root cause leaves
contexts := []*InterruptCtx{ctxB, ctxC}
// Convert to signal tree
signal := FromInterruptContexts(contexts)
// Assertions for the signal tree structure (should merge at D)
assert.NotNil(t, signal)
assert.Equal(t, "D", signal.ID)
assert.Len(t, signal.Subs, 2)
// Order of subs is not guaranteed, so we check for presence
subIDs := []string{signal.Subs[0].ID, signal.Subs[1].ID}
assert.Contains(t, subIDs, "B")
assert.Contains(t, subIDs, "C")
// Convert back to user-facing contexts
finalContexts := ToInterruptContexts(signal, nil)
// Assertions for the final list of contexts
assert.Len(t, finalContexts, 2)
finalIDs := []string{finalContexts[0].ID, finalContexts[1].ID}
assert.Contains(t, finalIDs, "B")
assert.Contains(t, finalIDs, "C")
// Check parent linking for one of the branches
var finalB *InterruptCtx
if finalContexts[0].ID == "B" {
finalB = finalContexts[0]
} else {
finalB = finalContexts[1]
}
assert.NotNil(t, finalB.Parent)
assert.Equal(t, "D", finalB.Parent.ID)
assert.Nil(t, finalB.Parent.Parent)
})
// Test Case 3: Nil and Empty Inputs
t.Run("NilAndEmpty", func(t *testing.T) {
assert.Nil(t, FromInterruptContexts(nil))
assert.Nil(t, FromInterruptContexts([]*InterruptCtx{}))
assert.Nil(t, ToInterruptContexts(nil, nil))
})
}
func TestSignalToPersistenceMaps(t *testing.T) {
// Test Case 1: Nil Signal
t.Run("NilSignal", func(t *testing.T) {
id2addr, id2state := SignalToPersistenceMaps(nil)
assert.NotNil(t, id2addr)
assert.NotNil(t, id2state)
assert.Empty(t, id2addr)
assert.Empty(t, id2state)
})
// Test Case 2: Single Node Signal
t.Run("SingleNode", func(t *testing.T) {
signal := &InterruptSignal{
ID: "node1",
Address: Address{
{Type: AddressSegmentAgent, ID: "agent1"},
},
InterruptState: InterruptState{
State: "test state",
LayerSpecificPayload: "test payload",
},
}
id2addr, id2state := SignalToPersistenceMaps(signal)
assert.Len(t, id2addr, 1)
assert.Len(t, id2state, 1)
assert.Equal(t, signal.Address, id2addr["node1"])
assert.Equal(t, signal.InterruptState, id2state["node1"])
})
// Test Case 3: Simple Tree Structure
t.Run("SimpleTree", func(t *testing.T) {
child1 := &InterruptSignal{
ID: "child1",
Address: Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
},
InterruptState: InterruptState{
State: "child1 state",
},
}
child2 := &InterruptSignal{
ID: "child2",
Address: Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool2"},
},
InterruptState: InterruptState{
State: "child2 state",
},
}
parent := &InterruptSignal{
ID: "parent",
Address: Address{
{Type: AddressSegmentAgent, ID: "agent1"},
},
InterruptState: InterruptState{
State: "parent state",
},
Subs: []*InterruptSignal{child1, child2},
}
id2addr, id2state := SignalToPersistenceMaps(parent)
// Should contain all 3 nodes
assert.Len(t, id2addr, 3)
assert.Len(t, id2state, 3)
// Check parent node
assert.Equal(t, parent.Address, id2addr["parent"])
assert.Equal(t, parent.InterruptState, id2state["parent"])
// Check child nodes
assert.Equal(t, child1.Address, id2addr["child1"])
assert.Equal(t, child1.InterruptState, id2state["child1"])
assert.Equal(t, child2.Address, id2addr["child2"])
assert.Equal(t, child2.InterruptState, id2state["child2"])
})
// Test Case 4: Deeply Nested Tree
t.Run("DeeplyNestedTree", func(t *testing.T) {
leaf1 := &InterruptSignal{
ID: "leaf1",
Address: Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
{Type: AddressSegmentNode, ID: "node1"},
},
InterruptState: InterruptState{
State: "leaf1 state",
},
}
leaf2 := &InterruptSignal{
ID: "leaf2",
Address: Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
{Type: AddressSegmentNode, ID: "node2"},
},
InterruptState: InterruptState{
State: "leaf2 state",
},
}
middle := &InterruptSignal{
ID: "middle",
Address: Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
},
InterruptState: InterruptState{
State: "middle state",
},
Subs: []*InterruptSignal{leaf1, leaf2},
}
root := &InterruptSignal{
ID: "root",
Address: Address{
{Type: AddressSegmentAgent, ID: "agent1"},
},
InterruptState: InterruptState{
State: "root state",
},
Subs: []*InterruptSignal{middle},
}
id2addr, id2state := SignalToPersistenceMaps(root)
// Should contain all 4 nodes
assert.Len(t, id2addr, 4)
assert.Len(t, id2state, 4)
// Verify all nodes are present
assert.Equal(t, root.Address, id2addr["root"])
assert.Equal(t, root.InterruptState, id2state["root"])
assert.Equal(t, middle.Address, id2addr["middle"])
assert.Equal(t, middle.InterruptState, id2state["middle"])
assert.Equal(t, leaf1.Address, id2addr["leaf1"])
assert.Equal(t, leaf1.InterruptState, id2state["leaf1"])
assert.Equal(t, leaf2.Address, id2addr["leaf2"])
assert.Equal(t, leaf2.InterruptState, id2state["leaf2"])
})
// Test Case 5: Complex Tree with Multiple Branches
t.Run("ComplexTree", func(t *testing.T) {
// Create a complex tree structure with multiple branches
branch1Leaf1 := &InterruptSignal{ID: "b1l1", Address: Address{{Type: AddressSegmentAgent, ID: "a1"}}, InterruptState: InterruptState{State: "b1l1"}}
branch1Leaf2 := &InterruptSignal{ID: "b1l2", Address: Address{{Type: AddressSegmentAgent, ID: "a1"}}, InterruptState: InterruptState{State: "b1l2"}}
branch1 := &InterruptSignal{ID: "b1", Address: Address{{Type: AddressSegmentAgent, ID: "a1"}}, InterruptState: InterruptState{State: "b1"}, Subs: []*InterruptSignal{branch1Leaf1, branch1Leaf2}}
branch2Leaf1 := &InterruptSignal{ID: "b2l1", Address: Address{{Type: AddressSegmentAgent, ID: "a1"}}, InterruptState: InterruptState{State: "b2l1"}}
branch2 := &InterruptSignal{ID: "b2", Address: Address{{Type: AddressSegmentAgent, ID: "a1"}}, InterruptState: InterruptState{State: "b2"}, Subs: []*InterruptSignal{branch2Leaf1}}
root := &InterruptSignal{ID: "root", Address: Address{{Type: AddressSegmentAgent, ID: "a1"}}, InterruptState: InterruptState{State: "root"}, Subs: []*InterruptSignal{branch1, branch2}}
id2addr, id2state := SignalToPersistenceMaps(root)
// Should contain all 6 nodes
assert.Len(t, id2addr, 6)
assert.Len(t, id2state, 6)
// Verify all nodes are present
expectedNodes := []string{"root", "b1", "b2", "b1l1", "b1l2", "b2l1"}
for _, nodeID := range expectedNodes {
assert.Contains(t, id2addr, nodeID)
assert.Contains(t, id2state, nodeID)
}
})
// Test Case 6: Empty InterruptState Values
t.Run("EmptyInterruptState", func(t *testing.T) {
signal := &InterruptSignal{
ID: "node1",
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
InterruptState: InterruptState{
// Empty state values
},
}
id2addr, id2state := SignalToPersistenceMaps(signal)
assert.Len(t, id2addr, 1)
assert.Len(t, id2state, 1)
assert.Equal(t, signal.Address, id2addr["node1"])
assert.Equal(t, signal.InterruptState, id2state["node1"])
})
}
func TestGetCurrentAddress(t *testing.T) {
// Test Case 1: No Address in Context
t.Run("NoAddressInContext", func(t *testing.T) {
ctx := context.Background()
addr := GetCurrentAddress(ctx)
assert.Nil(t, addr)
})
// Test Case 2: Address in Context
t.Run("AddressInContext", func(t *testing.T) {
ctx := context.Background()
expectedAddr := Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
}
// Create a context with address using internal addrCtx
runCtx := &addrCtx{
addr: expectedAddr,
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
addr := GetCurrentAddress(ctx)
assert.Equal(t, expectedAddr, addr)
})
}
func TestGetNextResumptionPoints(t *testing.T) {
// Test Case 1: No Resume Info in Context
t.Run("NoResumeInfo", func(t *testing.T) {
ctx := context.Background()
_, err := GetNextResumptionPoints(ctx)
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to get resume info")
})
// Test Case 2: Empty Resume Info
t.Run("EmptyResumeInfo", func(t *testing.T) {
ctx := context.Background()
rInfo := &globalResumeInfo{
id2Addr: make(map[string]Address),
}
ctx = context.WithValue(ctx, globalResumeInfoKey{}, rInfo)
points, err := GetNextResumptionPoints(ctx)
assert.NoError(t, err)
assert.Empty(t, points)
})
// Test Case 3: Valid Resume Points
t.Run("ValidResumePoints", func(t *testing.T) {
ctx := context.Background()
// Set up current address
currentAddr := Address{
{Type: AddressSegmentAgent, ID: "agent1"},
}
runCtx := &addrCtx{
addr: currentAddr,
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
// Set up resume info with child addresses
rInfo := &globalResumeInfo{
id2Addr: map[string]Address{
"child1": {
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
},
"child2": {
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool2"},
},
"unrelated": {
{Type: AddressSegmentAgent, ID: "agent2"},
},
},
}
ctx = context.WithValue(ctx, globalResumeInfoKey{}, rInfo)
points, err := GetNextResumptionPoints(ctx)
assert.NoError(t, err)
assert.Len(t, points, 2)
assert.True(t, points["tool1"])
assert.True(t, points["tool2"])
})
// Test Case 4: Root Address (Empty Parent)
t.Run("RootAddress", func(t *testing.T) {
ctx := context.Background()
// Empty current address (root)
runCtx := &addrCtx{
addr: Address{},
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
// Set up resume info with various addresses
rInfo := &globalResumeInfo{
id2Addr: map[string]Address{
"agent1": {
{Type: AddressSegmentAgent, ID: "agent1"},
},
"agent2": {
{Type: AddressSegmentAgent, ID: "agent2"},
},
},
}
ctx = context.WithValue(ctx, globalResumeInfoKey{}, rInfo)
points, err := GetNextResumptionPoints(ctx)
assert.NoError(t, err)
assert.Len(t, points, 2)
assert.True(t, points["agent1"])
assert.True(t, points["agent2"])
})
}
func TestBatchResumeWithData(t *testing.T) {
// Test Case 1: New Resume Data
t.Run("NewResumeData", func(t *testing.T) {
ctx := context.Background()
resumeData := map[string]any{
"id1": "data1",
"id2": "data2",
}
newCtx := BatchResumeWithData(ctx, resumeData)
// Verify the data was set correctly
rInfo, ok := newCtx.Value(globalResumeInfoKey{}).(*globalResumeInfo)
assert.True(t, ok)
assert.NotNil(t, rInfo)
assert.Equal(t, "data1", rInfo.id2ResumeData["id1"])
assert.Equal(t, "data2", rInfo.id2ResumeData["id2"])
})
// Test Case 2: Merge with Existing Resume Data
t.Run("MergeWithExisting", func(t *testing.T) {
ctx := context.Background()
// First call with initial data
initialData := map[string]any{
"id1": "initial",
}
ctx = BatchResumeWithData(ctx, initialData)
// Second call with additional data
additionalData := map[string]any{
"id2": "additional",
}
newCtx := BatchResumeWithData(ctx, additionalData)
// Verify both data sets are present
rInfo, ok := newCtx.Value(globalResumeInfoKey{}).(*globalResumeInfo)
assert.True(t, ok)
assert.NotNil(t, rInfo)
assert.Equal(t, "initial", rInfo.id2ResumeData["id1"])
assert.Equal(t, "additional", rInfo.id2ResumeData["id2"])
})
// Test Case 3: Empty Resume Data
t.Run("EmptyResumeData", func(t *testing.T) {
ctx := context.Background()
newCtx := BatchResumeWithData(ctx, map[string]any{})
rInfo, ok := newCtx.Value(globalResumeInfoKey{}).(*globalResumeInfo)
assert.True(t, ok)
assert.NotNil(t, rInfo)
assert.Empty(t, rInfo.id2ResumeData)
})
}
func TestGetInterruptState(t *testing.T) {
// Test Case 1: No Interrupt State
t.Run("NoInterruptState", func(t *testing.T) {
ctx := context.Background()
wasInterrupted, hasState, state := GetInterruptState[string](ctx)
assert.False(t, wasInterrupted)
assert.False(t, hasState)
assert.Equal(t, "", state)
})
// Test Case 2: With Interrupt State
t.Run("WithInterruptState", func(t *testing.T) {
ctx := context.Background()
// Create a context with interrupt state
runCtx := &addrCtx{
interruptState: &InterruptState{
State: "test state",
},
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
wasInterrupted, hasState, state := GetInterruptState[string](ctx)
assert.True(t, wasInterrupted)
assert.True(t, hasState)
assert.Equal(t, "test state", state)
})
// Test Case 3: Wrong Type for Interrupt State
t.Run("WrongType", func(t *testing.T) {
ctx := context.Background()
// Create a context with interrupt state of wrong type
runCtx := &addrCtx{
interruptState: &InterruptState{
State: 123, // int instead of string
},
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
wasInterrupted, hasState, state := GetInterruptState[string](ctx)
assert.True(t, wasInterrupted)
assert.False(t, hasState) // Should be false due to type mismatch
assert.Equal(t, "", state)
})
// Test Case 4: Nil Interrupt State
t.Run("NilInterruptState", func(t *testing.T) {
ctx := context.Background()
// Create a context with nil interrupt state
runCtx := &addrCtx{
interruptState: nil,
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
wasInterrupted, hasState, state := GetInterruptState[string](ctx)
assert.False(t, wasInterrupted) // Should be false because interruptState is nil
assert.False(t, hasState) // Should be false because state is nil
assert.Equal(t, "", state)
})
}
func TestGetResumeContext(t *testing.T) {
// Test Case 1: Not Resume Target
t.Run("NotResumeTarget", func(t *testing.T) {
ctx := context.Background()
isResumeTarget, hasData, data := GetResumeContext[string](ctx)
assert.False(t, isResumeTarget)
assert.False(t, hasData)
assert.Equal(t, "", data)
})
// Test Case 2: Resume Target with Data
t.Run("ResumeTargetWithData", func(t *testing.T) {
ctx := context.Background()
// Create a context as resume target with data
runCtx := &addrCtx{
isResumeTarget: true,
resumeData: "resume data",
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
isResumeTarget, hasData, data := GetResumeContext[string](ctx)
assert.True(t, isResumeTarget)
assert.True(t, hasData)
assert.Equal(t, "resume data", data)
})
// Test Case 3: Resume Target without Data
t.Run("ResumeTargetWithoutData", func(t *testing.T) {
ctx := context.Background()
// Create a context as resume target without data
runCtx := &addrCtx{
isResumeTarget: true,
resumeData: nil,
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
isResumeTarget, hasData, data := GetResumeContext[string](ctx)
assert.True(t, isResumeTarget)
assert.False(t, hasData)
assert.Equal(t, "", data)
})
// Test Case 4: Wrong Type for Resume Data
t.Run("WrongType", func(t *testing.T) {
ctx := context.Background()
// Create a context with resume data of wrong type
runCtx := &addrCtx{
isResumeTarget: true,
resumeData: 123, // int instead of string
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
isResumeTarget, hasData, data := GetResumeContext[string](ctx)
assert.True(t, isResumeTarget)
assert.False(t, hasData) // Should be false due to type mismatch
assert.Equal(t, "", data)
})
}
func TestWithLayerPayload(t *testing.T) {
// Test Case 1: Basic Usage
t.Run("BasicUsage", func(t *testing.T) {
config := &InterruptConfig{}
opt := WithLayerPayload("test payload")
opt(config)
assert.Equal(t, "test payload", config.LayerPayload)
})
// Test Case 2: Nil Payload
t.Run("NilPayload", func(t *testing.T) {
config := &InterruptConfig{LayerPayload: "existing"}
opt := WithLayerPayload(nil)
opt(config)
assert.Nil(t, config.LayerPayload)
})
// Test Case 3: Complex Payload
t.Run("ComplexPayload", func(t *testing.T) {
config := &InterruptConfig{}
payload := map[string]any{
"key1": "value1",
"key2": 123,
}
opt := WithLayerPayload(payload)
opt(config)
assert.Equal(t, payload, config.LayerPayload)
})
}
func TestInterruptFunction(t *testing.T) {
// Test Case 1: Simple Interrupt without SubContexts
t.Run("SimpleInterrupt", func(t *testing.T) {
ctx := context.Background()
// Create a context with a mock address
expectedAddr := Address{{Type: AddressSegmentAgent, ID: "test-agent"}}
runCtx := &addrCtx{
addr: expectedAddr,
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
info := "test info"
state := "test state"
signal, err := Interrupt(ctx, info, state, nil)
assert.NoError(t, err)
assert.NotNil(t, signal)
assert.NotEmpty(t, signal.ID)
assert.Equal(t, info, signal.Info)
assert.Equal(t, state, signal.State)
assert.True(t, signal.IsRootCause)
assert.Equal(t, expectedAddr, signal.Address)
})
// Test Case 2: Interrupt with SubContexts
t.Run("InterruptWithSubContexts", func(t *testing.T) {
ctx := context.Background()
// Create a context with a mock address
expectedAddr := Address{{Type: AddressSegmentAgent, ID: "parent-agent"}}
runCtx := &addrCtx{
addr: expectedAddr,
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
// Create sub contexts
subContexts := []*InterruptSignal{
{
ID: "child1",
Address: Address{{Type: AddressSegmentAgent, ID: "child1"}},
},
{
ID: "child2",
Address: Address{{Type: AddressSegmentAgent, ID: "child2"}},
},
}
info := "parent info"
state := "parent state"
signal, err := Interrupt(ctx, info, state, subContexts)
assert.NoError(t, err)
assert.NotNil(t, signal)
assert.NotEmpty(t, signal.ID)
assert.Equal(t, info, signal.Info)
assert.Equal(t, state, signal.State)
assert.False(t, signal.IsRootCause) // Should be false when there are sub contexts
assert.Len(t, signal.Subs, 2)
assert.Equal(t, "child1", signal.Subs[0].ID)
assert.Equal(t, "child2", signal.Subs[1].ID)
})
// Test Case 3: Interrupt with Options
t.Run("InterruptWithOptions", func(t *testing.T) {
ctx := context.Background()
// Create a context with a mock address
expectedAddr := Address{{Type: AddressSegmentAgent, ID: "test-agent"}}
runCtx := &addrCtx{
addr: expectedAddr,
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
info := "test info"
state := "test state"
layerPayload := "layer payload"
signal, err := Interrupt(ctx, info, state, nil, WithLayerPayload(layerPayload))
assert.NoError(t, err)
assert.NotNil(t, signal)
assert.Equal(t, layerPayload, signal.LayerSpecificPayload)
})
// Test Case 4: Empty SubContexts
t.Run("EmptySubContexts", func(t *testing.T) {
ctx := context.Background()
// Create a context with a mock address
expectedAddr := Address{{Type: AddressSegmentAgent, ID: "test-agent"}}
runCtx := &addrCtx{
addr: expectedAddr,
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
info := "test info"
state := "test state"
signal, err := Interrupt(ctx, info, state, []*InterruptSignal{})
assert.NoError(t, err)
assert.NotNil(t, signal)
assert.True(t, signal.IsRootCause) // Should be true when sub contexts is empty
assert.Empty(t, signal.Subs)
})
}
func TestAddressMethods(t *testing.T) {
// Test Case 1: Address.String()
t.Run("AddressString", func(t *testing.T) {
addr := Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
{Type: AddressSegmentNode, ID: "node1", SubID: "sub1"},
}
result := addr.String()
expected := "agent:agent1;tool:tool1;node:node1:sub1"
assert.Equal(t, expected, result)
})
// Test Case 2: Address.String() with empty address
t.Run("EmptyAddressString", func(t *testing.T) {
var addr Address
result := addr.String()
assert.Equal(t, "", result)
})
// Test Case 3: Address.Equals() with equal addresses
t.Run("AddressEquals", func(t *testing.T) {
addr1 := Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
}
addr2 := Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
}
assert.True(t, addr1.Equals(addr2))
})
// Test Case 4: Address.Equals() with different addresses
t.Run("AddressNotEquals", func(t *testing.T) {
addr1 := Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
}
addr2 := Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool2"},
}
assert.False(t, addr1.Equals(addr2))
})
// Test Case 5: Address.Equals() with different lengths
t.Run("AddressDifferentLengths", func(t *testing.T) {
addr1 := Address{
{Type: AddressSegmentAgent, ID: "agent1"},
{Type: AddressSegmentTool, ID: "tool1"},
}
addr2 := Address{
{Type: AddressSegmentAgent, ID: "agent1"},
}
assert.False(t, addr1.Equals(addr2))
})
// Test Case 6: Address.Equals() with SubID differences
t.Run("AddressSubIDDifference", func(t *testing.T) {
addr1 := Address{
{Type: AddressSegmentAgent, ID: "agent1", SubID: "sub1"},
}
addr2 := Address{
{Type: AddressSegmentAgent, ID: "agent1", SubID: "sub2"},
}
assert.False(t, addr1.Equals(addr2))
})
}
func TestAppendAddressSegment(t *testing.T) {
// Test Case 1: Append to empty address
t.Run("AppendToEmpty", func(t *testing.T) {
ctx := context.Background()
newCtx := AppendAddressSegment(ctx, AddressSegmentAgent, "agent1", "")
addr := GetCurrentAddress(newCtx)
assert.Len(t, addr, 1)
assert.Equal(t, AddressSegmentAgent, addr[0].Type)
assert.Equal(t, "agent1", addr[0].ID)
assert.Equal(t, "", addr[0].SubID)
})
// Test Case 2: Append to existing address
t.Run("AppendToExisting", func(t *testing.T) {
ctx := context.Background()
// First append
ctx = AppendAddressSegment(ctx, AddressSegmentAgent, "agent1", "")
// Second append
newCtx := AppendAddressSegment(ctx, AddressSegmentTool, "tool1", "call1")
addr := GetCurrentAddress(newCtx)
assert.Len(t, addr, 2)
assert.Equal(t, AddressSegmentAgent, addr[0].Type)
assert.Equal(t, "agent1", addr[0].ID)
assert.Equal(t, AddressSegmentTool, addr[1].Type)
assert.Equal(t, "tool1", addr[1].ID)
assert.Equal(t, "call1", addr[1].SubID)
})
// Test Case 3: Append with SubID
t.Run("AppendWithSubID", func(t *testing.T) {
ctx := context.Background()
newCtx := AppendAddressSegment(ctx, AddressSegmentTool, "tool1", "call123")
addr := GetCurrentAddress(newCtx)
assert.Len(t, addr, 1)
assert.Equal(t, AddressSegmentTool, addr[0].Type)
assert.Equal(t, "tool1", addr[0].ID)
assert.Equal(t, "call123", addr[0].SubID)
})
}
func TestPopulateInterruptState(t *testing.T) {
// Test Case 1: Populate with matching address
t.Run("PopulateMatchingAddress", func(t *testing.T) {
ctx := context.Background()
// Set up current address
currentAddr := Address{{Type: AddressSegmentAgent, ID: "agent1"}}
runCtx := &addrCtx{
addr: currentAddr,
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
// Set up interrupt state data
id2Addr := map[string]Address{
"interrupt1": currentAddr,
}
id2State := map[string]InterruptState{
"interrupt1": {State: "test state"},
}
newCtx := PopulateInterruptState(ctx, id2Addr, id2State)
// Verify the state was populated
wasInterrupted, hasState, state := GetInterruptState[string](newCtx)
assert.True(t, wasInterrupted)
assert.True(t, hasState)
assert.Equal(t, "test state", state)
})
// Test Case 2: Populate with non-matching address
t.Run("PopulateNonMatchingAddress", func(t *testing.T) {
ctx := context.Background()
// Set up current address
currentAddr := Address{{Type: AddressSegmentAgent, ID: "agent1"}}
runCtx := &addrCtx{
addr: currentAddr,
}
ctx = context.WithValue(ctx, addrCtxKey{}, runCtx)
// Set up interrupt state data with different address
id2Addr := map[string]Address{
"interrupt1": {{Type: AddressSegmentAgent, ID: "agent2"}},
}
id2State := map[string]InterruptState{
"interrupt1": {State: "test state"},
}
newCtx := PopulateInterruptState(ctx, id2Addr, id2State)
// Verify the state was NOT populated (no matching address)
wasInterrupted, hasState, state := GetInterruptState[string](newCtx)
assert.False(t, wasInterrupted)
assert.False(t, hasState)
assert.Equal(t, "", state)
})
// Test Case 3: Populate with empty data
t.Run("PopulateEmptyData", func(t *testing.T) {
ctx := context.Background()
newCtx := PopulateInterruptState(ctx, map[string]Address{}, map[string]InterruptState{})
// Verify no state was populated
wasInterrupted, hasState, state := GetInterruptState[string](newCtx)
assert.False(t, wasInterrupted)
assert.False(t, hasState)
assert.Equal(t, "", state)
})
}
func TestStringMethods(t *testing.T) {
// Test Case 1: InterruptSignal.Error()
t.Run("InterruptSignalError", func(t *testing.T) {
signal := &InterruptSignal{
ID: "test-id",
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
InterruptInfo: InterruptInfo{
Info: "test info",
},
InterruptState: InterruptState{
State: "test state",
LayerSpecificPayload: "test payload",
},
Subs: []*InterruptSignal{
{ID: "sub1"},
},
}
errorStr := signal.Error()
expectedContains := []string{
"interrupt signal:",
"ID=test-id",
"Addr=agent:agent1",
"Info=interrupt info: Info=test info, IsRootCause=false",
"State=interrupt state: State=test state, LayerSpecificPayload=test payload",
"SubsLen=1",
}
for _, expected := range expectedContains {
assert.Contains(t, errorStr, expected)
}
})
// Test Case 2: InterruptState.String()
t.Run("InterruptStateString", func(t *testing.T) {
state := &InterruptState{
State: "test state",
LayerSpecificPayload: "test payload",
}
result := state.String()
expected := "interrupt state: State=test state, LayerSpecificPayload=test payload"
assert.Equal(t, expected, result)
})
// Test Case 3: InterruptState.String() with nil
t.Run("InterruptStateStringNil", func(t *testing.T) {
var state *InterruptState
result := state.String()
assert.Equal(t, "", result)
})
// Test Case 4: InterruptInfo.String()
t.Run("InterruptInfoString", func(t *testing.T) {
info := &InterruptInfo{
Info: "test info",
IsRootCause: true,
}
result := info.String()
expected := "interrupt info: Info=test info, IsRootCause=true"
assert.Equal(t, expected, result)
})
// Test Case 5: InterruptInfo.String() with nil
t.Run("InterruptInfoStringNil", func(t *testing.T) {
var info *InterruptInfo
result := info.String()
assert.Equal(t, "", result)
})
}
func TestInterruptCtxEqualsWithoutID(t *testing.T) {
// Test Case 1: Equal contexts
t.Run("EqualContexts", func(t *testing.T) {
ctx1 := &InterruptCtx{
ID: "id1",
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
Info: "info1",
IsRootCause: true,
}
ctx2 := &InterruptCtx{
ID: "id2", // Different ID should be ignored
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
Info: "info1",
IsRootCause: true,
}
assert.True(t, ctx1.EqualsWithoutID(ctx2))
})
// Test Case 2: Different addresses
t.Run("DifferentAddresses", func(t *testing.T) {
ctx1 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
}
ctx2 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "agent2"}},
}
assert.False(t, ctx1.EqualsWithoutID(ctx2))
})
// Test Case 3: Different root cause flags
t.Run("DifferentRootCause", func(t *testing.T) {
ctx1 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
IsRootCause: true,
}
ctx2 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
IsRootCause: false,
}
assert.False(t, ctx1.EqualsWithoutID(ctx2))
})
// Test Case 4: Different info
t.Run("DifferentInfo", func(t *testing.T) {
ctx1 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
Info: "info1",
}
ctx2 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
Info: "info2",
}
assert.False(t, ctx1.EqualsWithoutID(ctx2))
})
// Test Case 5: Nil contexts
t.Run("NilContexts", func(t *testing.T) {
var ctx1 *InterruptCtx
var ctx2 *InterruptCtx
assert.True(t, ctx1.EqualsWithoutID(ctx2))
ctx3 := &InterruptCtx{}
assert.False(t, ctx1.EqualsWithoutID(ctx3))
assert.False(t, ctx3.EqualsWithoutID(ctx1))
})
// Test Case 6: With parent contexts
t.Run("WithParentContexts", func(t *testing.T) {
parent1 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "parent"}},
}
parent2 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "parent"}},
}
ctx1 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
Parent: parent1,
}
ctx2 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
Parent: parent2,
}
assert.True(t, ctx1.EqualsWithoutID(ctx2))
})
// Test Case 7: Different parent contexts
t.Run("DifferentParentContexts", func(t *testing.T) {
parent1 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "parent1"}},
}
parent2 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "parent2"}},
}
ctx1 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
Parent: parent1,
}
ctx2 := &InterruptCtx{
Address: Address{{Type: AddressSegmentAgent, ID: "agent1"}},
Parent: parent2,
}
assert.False(t, ctx1.EqualsWithoutID(ctx2))
})
}