blob: 4fe0ffbee7483c403a87f9a9ac4b99cabbc665d1 [file] [log] [blame]
package ast
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRewriteUnmarshalJSON(t *testing.T) {
tests := []struct {
name string
input string
expected Rewrite
}{
{
name: "Standard rewrite with editor and original",
input: `{
"@type": "koral:rewrite",
"editor": "termMapper",
"operation": "operation:mapping",
"scope": "foundry",
"original": {
"@type": "koral:term",
"foundry": "opennlp",
"key": "PIDAT",
"layer": "p",
"match": "match:eq"
}
}`,
expected: Rewrite{
Editor: "termMapper",
Operation: "operation:mapping",
Scope: "foundry",
Original: map[string]any{
"@type": "koral:term",
"foundry": "opennlp",
"key": "PIDAT",
"layer": "p",
"match": "match:eq",
},
},
},
{
name: "Legacy rewrite with source instead of editor",
input: `{
"@type": "koral:rewrite",
"source": "legacy-mapper",
"operation": "operation:mapping",
"scope": "foundry",
"src": "legacy-source"
}`,
expected: Rewrite{
Editor: "legacy-mapper",
Operation: "operation:mapping",
Scope: "foundry",
Src: "legacy-source",
},
},
{
name: "Legacy rewrite with origin instead of original/src",
input: `{
"@type": "koral:rewrite",
"editor": "termMapper",
"operation": "operation:mapping",
"scope": "foundry",
"origin": "legacy-origin"
}`,
expected: Rewrite{
Editor: "termMapper",
Operation: "operation:mapping",
Scope: "foundry",
Src: "legacy-origin",
},
},
{
name: "Precedence test: editor over source",
input: `{
"@type": "koral:rewrite",
"editor": "preferred-editor",
"source": "legacy-source",
"operation": "operation:mapping"
}`,
expected: Rewrite{
Editor: "preferred-editor",
Operation: "operation:mapping",
},
},
{
name: "Precedence test: original over src over origin",
input: `{
"@type": "koral:rewrite",
"editor": "termMapper",
"operation": "operation:mapping",
"original": "preferred-original",
"src": "middle-src",
"origin": "lowest-origin"
}`,
expected: Rewrite{
Editor: "termMapper",
Operation: "operation:mapping",
Original: "preferred-original",
},
},
{
name: "Precedence test: src over origin when no original",
input: `{
"@type": "koral:rewrite",
"editor": "termMapper",
"operation": "operation:mapping",
"src": "preferred-src",
"origin": "lowest-origin"
}`,
expected: Rewrite{
Editor: "termMapper",
Operation: "operation:mapping",
Src: "preferred-src",
},
},
{
name: "Only legacy fields",
input: `{
"@type": "koral:rewrite",
"source": "legacy-editor",
"operation": "operation:mapping",
"origin": "legacy-origin",
"_comment": "Legacy rewrite"
}`,
expected: Rewrite{
Editor: "legacy-editor",
Operation: "operation:mapping",
Src: "legacy-origin",
Comment: "Legacy rewrite",
},
},
{
name: "Mixed with comment",
input: `{
"@type": "koral:rewrite",
"editor": "termMapper",
"operation": "operation:mapping",
"scope": "foundry",
"src": "original-source",
"_comment": "This is a comment"
}`,
expected: Rewrite{
Editor: "termMapper",
Operation: "operation:mapping",
Scope: "foundry",
Src: "original-source",
Comment: "This is a comment",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rewrite Rewrite
err := json.Unmarshal([]byte(tt.input), &rewrite)
require.NoError(t, err)
assert.Equal(t, tt.expected, rewrite)
})
}
}
func TestRewriteArrayUnmarshal(t *testing.T) {
// Test unmarshaling an array of rewrites with mixed legacy and modern fields
input := `[
{
"@type": "koral:rewrite",
"editor": "termMapper",
"operation": "operation:mapping",
"original": "modern-original"
},
{
"@type": "koral:rewrite",
"source": "legacy-editor",
"operation": "operation:legacy",
"origin": "legacy-origin"
}
]`
var rewrites []Rewrite
err := json.Unmarshal([]byte(input), &rewrites)
require.NoError(t, err)
require.Len(t, rewrites, 2)
// Check first rewrite (modern)
assert.Equal(t, "termMapper", rewrites[0].Editor)
assert.Equal(t, "operation:mapping", rewrites[0].Operation)
assert.Equal(t, "modern-original", rewrites[0].Original)
// Check second rewrite (legacy)
assert.Equal(t, "legacy-editor", rewrites[1].Editor)
assert.Equal(t, "operation:legacy", rewrites[1].Operation)
assert.Equal(t, "legacy-origin", rewrites[1].Src)
}
func TestRewriteableInterface(t *testing.T) {
t.Run("Term implements Rewriteable", func(t *testing.T) {
term := &Term{Foundry: "opennlp", Key: "DET", Layer: "p", Match: MatchEqual}
var r Rewriteable = term
assert.Nil(t, r.GetRewrites())
rewrites := []Rewrite{{Editor: "test", Scope: "foundry"}}
r.SetRewrites(rewrites)
assert.Equal(t, rewrites, r.GetRewrites())
assert.Equal(t, rewrites, term.Rewrites)
})
t.Run("TermGroup implements Rewriteable", func(t *testing.T) {
tg := &TermGroup{
Operands: []Node{&Term{Key: "A", Match: MatchEqual}},
Relation: AndRelation,
}
var r Rewriteable = tg
assert.Nil(t, r.GetRewrites())
rewrites := []Rewrite{{Editor: "editor", Scope: "layer", Original: "old"}}
r.SetRewrites(rewrites)
assert.Equal(t, rewrites, r.GetRewrites())
assert.Equal(t, rewrites, tg.Rewrites)
})
t.Run("Token implements Rewriteable", func(t *testing.T) {
tok := &Token{Wrap: &Term{Key: "X", Match: MatchEqual}}
var r Rewriteable = tok
assert.Nil(t, r.GetRewrites())
rewrites := []Rewrite{{Editor: "mapper", Operation: "op"}}
r.SetRewrites(rewrites)
assert.Equal(t, rewrites, r.GetRewrites())
assert.Equal(t, rewrites, tok.Rewrites)
})
t.Run("SetRewrites to nil clears slice", func(t *testing.T) {
term := &Term{
Key: "DET",
Match: MatchEqual,
Rewrites: []Rewrite{{Editor: "x"}},
}
term.SetRewrites(nil)
assert.Nil(t, term.GetRewrites())
})
}
func TestAppendRewrite(t *testing.T) {
t.Run("Append to Term", func(t *testing.T) {
term := &Term{Key: "DET", Match: MatchEqual}
rw := Rewrite{Editor: "Koral-Mapper", Scope: "foundry", Original: "opennlp"}
AppendRewrite(term, rw)
assert.Equal(t, []Rewrite{rw}, term.Rewrites)
rw2 := Rewrite{Editor: "Koral-Mapper", Scope: "key", Original: "PIDAT"}
AppendRewrite(term, rw2)
assert.Equal(t, []Rewrite{rw, rw2}, term.Rewrites)
})
t.Run("Append to TermGroup", func(t *testing.T) {
tg := &TermGroup{
Operands: []Node{&Term{Key: "A", Match: MatchEqual}},
Relation: AndRelation,
}
rw := Rewrite{Editor: "editor", Original: "orig"}
AppendRewrite(tg, rw)
assert.Equal(t, []Rewrite{rw}, tg.Rewrites)
})
t.Run("Append to Token", func(t *testing.T) {
tok := &Token{Wrap: &Term{Key: "X", Match: MatchEqual}}
rw := Rewrite{Editor: "ed"}
AppendRewrite(tok, rw)
assert.Equal(t, []Rewrite{rw}, tok.Rewrites)
})
t.Run("Append to non-Rewriteable is no-op", func(t *testing.T) {
catchall := &CatchallNode{NodeType: "koral:span"}
rw := Rewrite{Editor: "test"}
AppendRewrite(catchall, rw)
// CatchallNode doesn't implement Rewriteable, so nothing happens
})
t.Run("Append to nil is no-op", func(t *testing.T) {
assert.NotPanics(t, func() {
AppendRewrite(nil, Rewrite{Editor: "x"})
})
})
}
func TestStripRewrites(t *testing.T) {
t.Run("Strips from Term", func(t *testing.T) {
term := &Term{
Key: "DET",
Match: MatchEqual,
Rewrites: []Rewrite{{Editor: "a"}, {Editor: "b"}},
}
StripRewrites(term)
assert.Nil(t, term.Rewrites)
})
t.Run("Strips from Token and its Wrap", func(t *testing.T) {
tok := &Token{
Wrap: &Term{
Key: "DET",
Match: MatchEqual,
Rewrites: []Rewrite{{Editor: "inner"}},
},
Rewrites: []Rewrite{{Editor: "outer"}},
}
StripRewrites(tok)
assert.Nil(t, tok.Rewrites)
assert.Nil(t, tok.Wrap.(*Term).Rewrites)
})
t.Run("Strips from TermGroup and all operands", func(t *testing.T) {
tg := &TermGroup{
Operands: []Node{
&Term{Key: "A", Match: MatchEqual, Rewrites: []Rewrite{{Editor: "e1"}}},
&Term{Key: "B", Match: MatchEqual, Rewrites: []Rewrite{{Editor: "e2"}}},
},
Relation: AndRelation,
Rewrites: []Rewrite{{Editor: "group"}},
}
StripRewrites(tg)
assert.Nil(t, tg.Rewrites)
assert.Nil(t, tg.Operands[0].(*Term).Rewrites)
assert.Nil(t, tg.Operands[1].(*Term).Rewrites)
})
t.Run("Strips recursively from CatchallNode", func(t *testing.T) {
catchall := &CatchallNode{
NodeType: "koral:group",
Wrap: &Term{
Key: "W",
Match: MatchEqual,
Rewrites: []Rewrite{{Editor: "wrap-ed"}},
},
Operands: []Node{
&Token{
Wrap: &Term{Key: "X", Match: MatchEqual, Rewrites: []Rewrite{{Editor: "deep"}}},
Rewrites: []Rewrite{{Editor: "tok"}},
},
},
}
StripRewrites(catchall)
assert.Nil(t, catchall.Wrap.(*Term).Rewrites)
tok := catchall.Operands[0].(*Token)
assert.Nil(t, tok.Rewrites)
assert.Nil(t, tok.Wrap.(*Term).Rewrites)
})
t.Run("Nil node does not panic", func(t *testing.T) {
assert.NotPanics(t, func() {
StripRewrites(nil)
})
})
t.Run("Already empty rewrites stays nil", func(t *testing.T) {
term := &Term{Key: "DET", Match: MatchEqual}
StripRewrites(term)
assert.Nil(t, term.Rewrites)
})
}
func TestRewriteMarshalJSON(t *testing.T) {
// Test that marshaling works correctly and maintains the modern field names
rewrite := Rewrite{
Editor: "termMapper",
Operation: "operation:mapping",
Scope: "foundry",
Src: "source-value",
Comment: "Test comment",
Original: "original-value",
}
data, err := json.Marshal(rewrite)
require.NoError(t, err)
// Parse back to verify structure
var result map[string]any
err = json.Unmarshal(data, &result)
require.NoError(t, err)
assert.Equal(t, "termMapper", result["editor"])
assert.Equal(t, "operation:mapping", result["operation"])
assert.Equal(t, "foundry", result["scope"])
assert.Equal(t, "source-value", result["src"])
assert.Equal(t, "Test comment", result["_comment"])
assert.Equal(t, "original-value", result["original"])
// Ensure legacy fields are not present in output
assert.NotContains(t, result, "source")
assert.NotContains(t, result, "origin")
}