blob: 49fdbe39b698cafee715a6155dc4599e97319fe4 [file] [log] [blame]
package ast
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNodeTypes(t *testing.T) {
tests := []struct {
name string
node Node
expected NodeType
}{
{
name: "Token node returns correct type",
node: &Token{Wrap: &Term{}},
expected: TokenNode,
},
{
name: "TermGroup node returns correct type",
node: &TermGroup{
Operands: []Node{&Term{}},
Relation: AndRelation,
},
expected: TermGroupNode,
},
{
name: "Term node returns correct type",
node: &Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
},
expected: TermNode,
},
{
name: "Rewrite node returns correct type",
node: &Rewrite{
Editor: "Kustvakt",
Operation: "operation:injection",
Scope: "foundry",
Src: "Kustvakt",
},
expected: RewriteNode,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.node.Type())
})
}
}
func TestTermGroupConstruction(t *testing.T) {
term1 := &Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
}
term2 := &Term{
Foundry: "opennlp",
Key: "AdjType",
Layer: "m",
Match: MatchEqual,
Value: "Pdt",
}
rewrites := []Rewrite{
{
Editor: "Kustvakt",
Operation: "operation:injection",
Scope: "foundry",
Src: "Kustvakt",
Comment: "Default foundry has been added.",
},
}
group := &TermGroup{
Operands: []Node{term1, term2},
Relation: AndRelation,
Rewrites: rewrites,
}
assert.Len(t, group.Operands, 2)
assert.Equal(t, AndRelation, group.Relation)
assert.Equal(t, TermGroupNode, group.Type())
assert.Equal(t, rewrites, group.Rewrites)
// Test operands are correctly set
assert.Equal(t, term1, group.Operands[0])
assert.Equal(t, term2, group.Operands[1])
}
func TestTokenConstruction(t *testing.T) {
term := &Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
}
rewrites := []Rewrite{
{
Editor: "Kustvakt",
Operation: "operation:injection",
Scope: "foundry",
Src: "Kustvakt",
Comment: "Default foundry has been added.",
},
}
token := &Token{
Wrap: term,
Rewrites: rewrites,
}
assert.Equal(t, TokenNode, token.Type())
assert.Equal(t, term, token.Wrap)
assert.Equal(t, rewrites, token.Rewrites)
}
func TestTermConstruction(t *testing.T) {
tests := []struct {
name string
term *Term
foundry string
key string
layer string
match MatchType
hasValue bool
value string
rewrites []Rewrite
}{
{
name: "Term without value",
term: &Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
},
foundry: "opennlp",
key: "DET",
layer: "p",
match: MatchEqual,
hasValue: false,
},
{
name: "Term with value",
term: &Term{
Foundry: "opennlp",
Key: "AdjType",
Layer: "m",
Match: MatchEqual,
Value: "Pdt",
},
foundry: "opennlp",
key: "AdjType",
layer: "m",
match: MatchEqual,
hasValue: true,
value: "Pdt",
},
{
name: "Term with not equal match",
term: &Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchNotEqual,
},
foundry: "opennlp",
key: "DET",
layer: "p",
match: MatchNotEqual,
hasValue: false,
},
{
name: "Term with rewrites",
term: &Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
Rewrites: []Rewrite{
{
Editor: "Kustvakt",
Operation: "operation:injection",
Scope: "foundry",
Src: "Kustvakt",
Comment: "Default foundry has been added.",
},
},
},
foundry: "opennlp",
key: "DET",
layer: "p",
match: MatchEqual,
hasValue: false,
rewrites: []Rewrite{
{
Editor: "Kustvakt",
Operation: "operation:injection",
Scope: "foundry",
Src: "Kustvakt",
Comment: "Default foundry has been added.",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, TermNode, tt.term.Type())
assert.Equal(t, tt.foundry, tt.term.Foundry)
assert.Equal(t, tt.key, tt.term.Key)
assert.Equal(t, tt.layer, tt.term.Layer)
assert.Equal(t, tt.match, tt.term.Match)
if tt.hasValue {
assert.Equal(t, tt.value, tt.term.Value)
} else {
assert.Empty(t, tt.term.Value)
}
if tt.rewrites != nil {
assert.Equal(t, tt.rewrites, tt.term.Rewrites)
} else {
assert.Empty(t, tt.term.Rewrites)
}
})
}
}
func TestPatternAndReplacement(t *testing.T) {
// Create a simple pattern
patternTerm := &Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
}
pattern := Pattern{Root: patternTerm}
// Create a simple replacement
replacementTerm := &Term{
Foundry: "opennlp",
Key: "COMBINED_DET",
Layer: "p",
Match: MatchEqual,
}
replacement := Replacement{Root: replacementTerm}
// Test pattern
assert.NotNil(t, pattern.Root)
assert.Equal(t, patternTerm, pattern.Root)
// Test replacement
assert.NotNil(t, replacement.Root)
assert.Equal(t, replacementTerm, replacement.Root)
}
func TestCatchallNode(t *testing.T) {
tests := []struct {
name string
nodeType string
content string
wrap Node
operands []Node
expectType NodeType
}{
{
name: "CatchallNode with custom type",
nodeType: "customType",
content: `{"key": "value"}`,
expectType: NodeType("customType"),
},
{
name: "CatchallNode with wrapped term",
nodeType: "wrapper",
content: `{"key": "value"}`,
wrap: &Term{
Foundry: "test",
Key: "TEST",
Layer: "x",
Match: MatchEqual,
},
expectType: NodeType("wrapper"),
},
{
name: "CatchallNode with operands",
nodeType: "custom_group",
content: `{"key": "value"}`,
operands: []Node{
&Term{Foundry: "test1", Key: "TEST1", Layer: "x", Match: MatchEqual},
&Term{Foundry: "test2", Key: "TEST2", Layer: "y", Match: MatchEqual},
},
expectType: NodeType("custom_group"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rawContent := json.RawMessage(tt.content)
node := &CatchallNode{
NodeType: tt.nodeType,
RawContent: rawContent,
Wrap: tt.wrap,
Operands: tt.operands,
}
assert.Equal(t, tt.expectType, node.Type())
if tt.wrap != nil {
assert.Equal(t, tt.wrap, node.Wrap)
}
if tt.operands != nil {
assert.Equal(t, tt.operands, node.Operands)
}
assert.Equal(t, rawContent, node.RawContent)
})
}
}
func TestRewriteConstruction(t *testing.T) {
rewrite := &Rewrite{
Editor: "Kustvakt",
Operation: "operation:injection",
Scope: "foundry",
Src: "Kustvakt",
Comment: "Default foundry has been added.",
}
assert.Equal(t, RewriteNode, rewrite.Type())
assert.Equal(t, "Kustvakt", rewrite.Editor)
assert.Equal(t, "operation:injection", rewrite.Operation)
assert.Equal(t, "foundry", rewrite.Scope)
assert.Equal(t, "Kustvakt", rewrite.Src)
assert.Equal(t, "Default foundry has been added.", rewrite.Comment)
}
func TestComplexNestedStructures(t *testing.T) {
// Test nested tokens and term groups
termGroup := &TermGroup{
Operands: []Node{
&Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
},
&Term{
Foundry: "opennlp",
Key: "AdjType",
Layer: "m",
Match: MatchEqual,
Value: "Pdt",
},
},
Relation: AndRelation,
}
token := &Token{
Wrap: termGroup,
}
assert.Equal(t, TokenNode, token.Type())
assert.NotNil(t, token.Wrap)
assert.Equal(t, TermGroupNode, token.Wrap.Type())
// Test that the nested structure is correct
if tg, ok := token.Wrap.(*TermGroup); ok {
assert.Equal(t, 2, len(tg.Operands))
assert.Equal(t, AndRelation, tg.Relation)
}
}
func TestEdgeCases(t *testing.T) {
tests := []struct {
name string
test func(t *testing.T)
}{
{
name: "Empty TermGroup",
test: func(t *testing.T) {
group := &TermGroup{
Operands: []Node{},
Relation: AndRelation,
}
assert.Empty(t, group.Operands)
assert.Equal(t, AndRelation, group.Relation)
},
},
{
name: "Token with nil wrap",
test: func(t *testing.T) {
token := &Token{Wrap: nil}
assert.Equal(t, TokenNode, token.Type())
assert.Nil(t, token.Wrap)
},
},
{
name: "Term with empty strings",
test: func(t *testing.T) {
term := &Term{
Foundry: "",
Key: "",
Layer: "",
Match: MatchEqual,
Value: "",
}
assert.Equal(t, TermNode, term.Type())
assert.Empty(t, term.Foundry)
assert.Empty(t, term.Key)
assert.Empty(t, term.Layer)
assert.Empty(t, term.Value)
},
},
{
name: "Complex Pattern and Replacement",
test: func(t *testing.T) {
// Create a complex pattern
patternGroup := &TermGroup{
Operands: []Node{
&Term{Foundry: "f1", Key: "k1", Layer: "l1", Match: MatchEqual},
&Token{Wrap: &Term{Foundry: "f2", Key: "k2", Layer: "l2", Match: MatchNotEqual}},
},
Relation: OrRelation,
}
pattern := Pattern{Root: patternGroup}
// Create a complex replacement
replacementGroup := &TermGroup{
Operands: []Node{
&Term{Foundry: "f3", Key: "k3", Layer: "l3", Match: MatchEqual},
&Term{Foundry: "f4", Key: "k4", Layer: "l4", Match: MatchEqual},
},
Relation: AndRelation,
}
replacement := Replacement{Root: replacementGroup}
assert.Equal(t, TermGroupNode, pattern.Root.Type())
assert.Equal(t, TermGroupNode, replacement.Root.Type())
},
},
}
for _, tt := range tests {
t.Run(tt.name, tt.test)
}
}
func TestCloneMethod(t *testing.T) {
tests := []struct {
name string
node Node
}{
{
name: "Clone Term",
node: &Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
Value: "test",
Rewrites: []Rewrite{
{
Editor: "test",
Scope: "foundry",
},
},
},
},
{
name: "Clone Token",
node: &Token{
Wrap: &Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
},
Rewrites: []Rewrite{
{
Editor: "test",
Scope: "layer",
},
},
},
},
{
name: "Clone TermGroup",
node: &TermGroup{
Operands: []Node{
&Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
},
&Term{
Foundry: "opennlp",
Key: "AdjType",
Layer: "m",
Match: MatchEqual,
Value: "Pdt",
},
},
Relation: AndRelation,
Rewrites: []Rewrite{
{
Editor: "test",
Scope: "foundry",
},
},
},
},
{
name: "Clone CatchallNode",
node: &CatchallNode{
NodeType: "koral:unknown",
RawContent: []byte(`{"@type":"koral:unknown","test":"value"}`),
Wrap: &Term{
Foundry: "opennlp",
Key: "DET",
Layer: "p",
Match: MatchEqual,
},
Operands: []Node{
&Term{
Foundry: "opennlp",
Key: "AdjType",
Layer: "m",
Match: MatchEqual,
Value: "Pdt",
},
},
},
},
{
name: "Clone Rewrite",
node: &Rewrite{
Editor: "termMapper",
Operation: "injection",
Scope: "foundry",
Src: "test",
Comment: "test comment",
Original: "original_value",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cloned := tt.node.Clone()
// Check that the clone is not the same instance
assert.NotSame(t, tt.node, cloned)
// Check that the clone has the same type
assert.Equal(t, tt.node.Type(), cloned.Type())
// Check that nodes are equal (deep comparison)
assert.True(t, NodesEqual(tt.node, cloned))
// Test that modifying the clone doesn't affect the original
switch original := tt.node.(type) {
case *Term:
clonedTerm := cloned.(*Term)
clonedTerm.Foundry = "modified"
assert.NotEqual(t, original.Foundry, clonedTerm.Foundry)
case *Token:
clonedToken := cloned.(*Token)
if clonedToken.Wrap != nil {
if termWrap, ok := clonedToken.Wrap.(*Term); ok {
termWrap.Foundry = "modified"
if originalWrap, ok := original.Wrap.(*Term); ok {
assert.NotEqual(t, originalWrap.Foundry, termWrap.Foundry)
}
}
}
case *TermGroup:
clonedGroup := cloned.(*TermGroup)
clonedGroup.Relation = OrRelation
assert.NotEqual(t, original.Relation, clonedGroup.Relation)
case *CatchallNode:
clonedCatchall := cloned.(*CatchallNode)
clonedCatchall.NodeType = "modified"
assert.NotEqual(t, original.NodeType, clonedCatchall.NodeType)
case *Rewrite:
clonedRewrite := cloned.(*Rewrite)
clonedRewrite.Editor = "modified"
assert.NotEqual(t, original.Editor, clonedRewrite.Editor)
}
})
}
}
func TestCloneNilNodes(t *testing.T) {
// Test cloning nodes with nil fields
tests := []struct {
name string
node Node
}{
{
name: "Token with nil wrap",
node: &Token{Wrap: nil},
},
{
name: "TermGroup with empty operands",
node: &TermGroup{
Operands: []Node{},
Relation: AndRelation,
},
},
{
name: "CatchallNode with nil wrap and operands",
node: &CatchallNode{
NodeType: "koral:unknown",
RawContent: nil,
Wrap: nil,
Operands: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cloned := tt.node.Clone()
assert.NotSame(t, tt.node, cloned)
assert.Equal(t, tt.node.Type(), cloned.Type())
assert.True(t, NodesEqual(tt.node, cloned))
})
}
}
func TestApplyFoundryAndLayerOverrides(t *testing.T) {
tests := []struct {
name string
node Node
foundry string
layer string
expectedChanges func(t *testing.T, node Node)
}{
{
name: "Apply foundry and layer to Term",
node: &Term{
Foundry: "original",
Key: "DET",
Layer: "original",
Match: MatchEqual,
},
foundry: "new_foundry",
layer: "new_layer",
expectedChanges: func(t *testing.T, node Node) {
term := node.(*Term)
assert.Equal(t, "new_foundry", term.Foundry)
assert.Equal(t, "new_layer", term.Layer)
},
},
{
name: "Apply only foundry to Term",
node: &Term{
Foundry: "original",
Key: "DET",
Layer: "original",
Match: MatchEqual,
},
foundry: "new_foundry",
layer: "",
expectedChanges: func(t *testing.T, node Node) {
term := node.(*Term)
assert.Equal(t, "new_foundry", term.Foundry)
assert.Equal(t, "original", term.Layer) // Should remain unchanged
},
},
{
name: "Apply to TermGroup",
node: &TermGroup{
Operands: []Node{
&Term{
Foundry: "original1",
Key: "DET",
Layer: "original1",
Match: MatchEqual,
},
&Term{
Foundry: "original2",
Key: "AdjType",
Layer: "original2",
Match: MatchEqual,
Value: "Pdt",
},
},
Relation: AndRelation,
},
foundry: "new_foundry",
layer: "new_layer",
expectedChanges: func(t *testing.T, node Node) {
termGroup := node.(*TermGroup)
for _, operand := range termGroup.Operands {
if term, ok := operand.(*Term); ok {
assert.Equal(t, "new_foundry", term.Foundry)
assert.Equal(t, "new_layer", term.Layer)
}
}
},
},
{
name: "Apply to Token with wrapped Term",
node: &Token{
Wrap: &Term{
Foundry: "original",
Key: "DET",
Layer: "original",
Match: MatchEqual,
},
},
foundry: "new_foundry",
layer: "new_layer",
expectedChanges: func(t *testing.T, node Node) {
token := node.(*Token)
if term, ok := token.Wrap.(*Term); ok {
assert.Equal(t, "new_foundry", term.Foundry)
assert.Equal(t, "new_layer", term.Layer)
}
},
},
{
name: "Apply to CatchallNode",
node: &CatchallNode{
NodeType: "koral:unknown",
Wrap: &Term{
Foundry: "original",
Key: "DET",
Layer: "original",
Match: MatchEqual,
},
Operands: []Node{
&Term{
Foundry: "original2",
Key: "AdjType",
Layer: "original2",
Match: MatchEqual,
Value: "Pdt",
},
},
},
foundry: "new_foundry",
layer: "new_layer",
expectedChanges: func(t *testing.T, node Node) {
catchall := node.(*CatchallNode)
if term, ok := catchall.Wrap.(*Term); ok {
assert.Equal(t, "new_foundry", term.Foundry)
assert.Equal(t, "new_layer", term.Layer)
}
for _, operand := range catchall.Operands {
if term, ok := operand.(*Term); ok {
assert.Equal(t, "new_foundry", term.Foundry)
assert.Equal(t, "new_layer", term.Layer)
}
}
},
},
{
name: "Apply to nested structure",
node: &Token{
Wrap: &TermGroup{
Operands: []Node{
&Term{
Foundry: "original1",
Key: "DET",
Layer: "original1",
Match: MatchEqual,
},
&Token{
Wrap: &Term{
Foundry: "original2",
Key: "AdjType",
Layer: "original2",
Match: MatchEqual,
Value: "Pdt",
},
},
},
Relation: AndRelation,
},
},
foundry: "new_foundry",
layer: "new_layer",
expectedChanges: func(t *testing.T, node Node) {
token := node.(*Token)
if termGroup, ok := token.Wrap.(*TermGroup); ok {
for _, operand := range termGroup.Operands {
switch op := operand.(type) {
case *Term:
assert.Equal(t, "new_foundry", op.Foundry)
assert.Equal(t, "new_layer", op.Layer)
case *Token:
if innerTerm, ok := op.Wrap.(*Term); ok {
assert.Equal(t, "new_foundry", innerTerm.Foundry)
assert.Equal(t, "new_layer", innerTerm.Layer)
}
}
}
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clone the node to avoid modifying the original test data
cloned := tt.node.Clone()
// Apply the overrides
ApplyFoundryAndLayerOverrides(cloned, tt.foundry, tt.layer)
// Check the expected changes
tt.expectedChanges(t, cloned)
})
}
}
func TestApplyFoundryAndLayerOverridesNilNode(t *testing.T) {
// Test that applying overrides to a nil node doesn't panic
assert.NotPanics(t, func() {
ApplyFoundryAndLayerOverrides(nil, "foundry", "layer")
})
}
func TestApplyFoundryAndLayerOverridesEmptyValues(t *testing.T) {
// Test applying empty foundry and layer values
term := &Term{
Foundry: "original_foundry",
Key: "DET",
Layer: "original_layer",
Match: MatchEqual,
}
ApplyFoundryAndLayerOverrides(term, "", "")
// Values should remain unchanged
assert.Equal(t, "original_foundry", term.Foundry)
assert.Equal(t, "original_layer", term.Layer)
}