blob: 4fe0ffbee7483c403a87f9a9ac4b99cabbc665d1 [file] [log] [blame]
Akron8f1970f2025-05-30 12:52:03 +02001package ast
2
3import (
4 "encoding/json"
5 "testing"
6
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/require"
9)
10
11func TestRewriteUnmarshalJSON(t *testing.T) {
12 tests := []struct {
13 name string
14 input string
15 expected Rewrite
16 }{
17 {
18 name: "Standard rewrite with editor and original",
19 input: `{
20 "@type": "koral:rewrite",
21 "editor": "termMapper",
22 "operation": "operation:mapping",
23 "scope": "foundry",
24 "original": {
25 "@type": "koral:term",
26 "foundry": "opennlp",
27 "key": "PIDAT",
28 "layer": "p",
29 "match": "match:eq"
30 }
31 }`,
32 expected: Rewrite{
33 Editor: "termMapper",
34 Operation: "operation:mapping",
35 Scope: "foundry",
36 Original: map[string]any{
37 "@type": "koral:term",
38 "foundry": "opennlp",
39 "key": "PIDAT",
40 "layer": "p",
41 "match": "match:eq",
42 },
43 },
44 },
45 {
46 name: "Legacy rewrite with source instead of editor",
47 input: `{
48 "@type": "koral:rewrite",
49 "source": "legacy-mapper",
50 "operation": "operation:mapping",
51 "scope": "foundry",
52 "src": "legacy-source"
53 }`,
54 expected: Rewrite{
55 Editor: "legacy-mapper",
56 Operation: "operation:mapping",
57 Scope: "foundry",
58 Src: "legacy-source",
59 },
60 },
61 {
62 name: "Legacy rewrite with origin instead of original/src",
63 input: `{
64 "@type": "koral:rewrite",
65 "editor": "termMapper",
66 "operation": "operation:mapping",
67 "scope": "foundry",
68 "origin": "legacy-origin"
69 }`,
70 expected: Rewrite{
71 Editor: "termMapper",
72 Operation: "operation:mapping",
73 Scope: "foundry",
74 Src: "legacy-origin",
75 },
76 },
77 {
78 name: "Precedence test: editor over source",
79 input: `{
80 "@type": "koral:rewrite",
81 "editor": "preferred-editor",
82 "source": "legacy-source",
83 "operation": "operation:mapping"
84 }`,
85 expected: Rewrite{
86 Editor: "preferred-editor",
87 Operation: "operation:mapping",
88 },
89 },
90 {
91 name: "Precedence test: original over src over origin",
92 input: `{
93 "@type": "koral:rewrite",
94 "editor": "termMapper",
95 "operation": "operation:mapping",
96 "original": "preferred-original",
97 "src": "middle-src",
98 "origin": "lowest-origin"
99 }`,
100 expected: Rewrite{
101 Editor: "termMapper",
102 Operation: "operation:mapping",
103 Original: "preferred-original",
104 },
105 },
106 {
107 name: "Precedence test: src over origin when no original",
108 input: `{
109 "@type": "koral:rewrite",
110 "editor": "termMapper",
111 "operation": "operation:mapping",
112 "src": "preferred-src",
113 "origin": "lowest-origin"
114 }`,
115 expected: Rewrite{
116 Editor: "termMapper",
117 Operation: "operation:mapping",
118 Src: "preferred-src",
119 },
120 },
121 {
122 name: "Only legacy fields",
123 input: `{
124 "@type": "koral:rewrite",
125 "source": "legacy-editor",
126 "operation": "operation:mapping",
127 "origin": "legacy-origin",
128 "_comment": "Legacy rewrite"
129 }`,
130 expected: Rewrite{
131 Editor: "legacy-editor",
132 Operation: "operation:mapping",
133 Src: "legacy-origin",
134 Comment: "Legacy rewrite",
135 },
136 },
137 {
138 name: "Mixed with comment",
139 input: `{
140 "@type": "koral:rewrite",
141 "editor": "termMapper",
142 "operation": "operation:mapping",
143 "scope": "foundry",
144 "src": "original-source",
145 "_comment": "This is a comment"
146 }`,
147 expected: Rewrite{
148 Editor: "termMapper",
149 Operation: "operation:mapping",
150 Scope: "foundry",
151 Src: "original-source",
152 Comment: "This is a comment",
153 },
154 },
155 }
156
157 for _, tt := range tests {
158 t.Run(tt.name, func(t *testing.T) {
159 var rewrite Rewrite
160 err := json.Unmarshal([]byte(tt.input), &rewrite)
161 require.NoError(t, err)
162 assert.Equal(t, tt.expected, rewrite)
163 })
164 }
165}
166
167func TestRewriteArrayUnmarshal(t *testing.T) {
168 // Test unmarshaling an array of rewrites with mixed legacy and modern fields
169 input := `[
170 {
171 "@type": "koral:rewrite",
172 "editor": "termMapper",
173 "operation": "operation:mapping",
174 "original": "modern-original"
175 },
176 {
177 "@type": "koral:rewrite",
178 "source": "legacy-editor",
179 "operation": "operation:legacy",
180 "origin": "legacy-origin"
181 }
182 ]`
183
184 var rewrites []Rewrite
185 err := json.Unmarshal([]byte(input), &rewrites)
186 require.NoError(t, err)
187 require.Len(t, rewrites, 2)
188
189 // Check first rewrite (modern)
190 assert.Equal(t, "termMapper", rewrites[0].Editor)
191 assert.Equal(t, "operation:mapping", rewrites[0].Operation)
192 assert.Equal(t, "modern-original", rewrites[0].Original)
193
194 // Check second rewrite (legacy)
195 assert.Equal(t, "legacy-editor", rewrites[1].Editor)
196 assert.Equal(t, "operation:legacy", rewrites[1].Operation)
197 assert.Equal(t, "legacy-origin", rewrites[1].Src)
198}
199
Akron8414ae52026-05-19 13:31:14 +0200200func TestRewriteableInterface(t *testing.T) {
201 t.Run("Term implements Rewriteable", func(t *testing.T) {
202 term := &Term{Foundry: "opennlp", Key: "DET", Layer: "p", Match: MatchEqual}
203
204 var r Rewriteable = term
205 assert.Nil(t, r.GetRewrites())
206
207 rewrites := []Rewrite{{Editor: "test", Scope: "foundry"}}
208 r.SetRewrites(rewrites)
209 assert.Equal(t, rewrites, r.GetRewrites())
210 assert.Equal(t, rewrites, term.Rewrites)
211 })
212
213 t.Run("TermGroup implements Rewriteable", func(t *testing.T) {
214 tg := &TermGroup{
215 Operands: []Node{&Term{Key: "A", Match: MatchEqual}},
216 Relation: AndRelation,
217 }
218
219 var r Rewriteable = tg
220 assert.Nil(t, r.GetRewrites())
221
222 rewrites := []Rewrite{{Editor: "editor", Scope: "layer", Original: "old"}}
223 r.SetRewrites(rewrites)
224 assert.Equal(t, rewrites, r.GetRewrites())
225 assert.Equal(t, rewrites, tg.Rewrites)
226 })
227
228 t.Run("Token implements Rewriteable", func(t *testing.T) {
229 tok := &Token{Wrap: &Term{Key: "X", Match: MatchEqual}}
230
231 var r Rewriteable = tok
232 assert.Nil(t, r.GetRewrites())
233
234 rewrites := []Rewrite{{Editor: "mapper", Operation: "op"}}
235 r.SetRewrites(rewrites)
236 assert.Equal(t, rewrites, r.GetRewrites())
237 assert.Equal(t, rewrites, tok.Rewrites)
238 })
239
240 t.Run("SetRewrites to nil clears slice", func(t *testing.T) {
241 term := &Term{
242 Key: "DET",
243 Match: MatchEqual,
244 Rewrites: []Rewrite{{Editor: "x"}},
245 }
246 term.SetRewrites(nil)
247 assert.Nil(t, term.GetRewrites())
248 })
249}
250
251func TestAppendRewrite(t *testing.T) {
252 t.Run("Append to Term", func(t *testing.T) {
253 term := &Term{Key: "DET", Match: MatchEqual}
254 rw := Rewrite{Editor: "Koral-Mapper", Scope: "foundry", Original: "opennlp"}
255
256 AppendRewrite(term, rw)
257 assert.Equal(t, []Rewrite{rw}, term.Rewrites)
258
259 rw2 := Rewrite{Editor: "Koral-Mapper", Scope: "key", Original: "PIDAT"}
260 AppendRewrite(term, rw2)
261 assert.Equal(t, []Rewrite{rw, rw2}, term.Rewrites)
262 })
263
264 t.Run("Append to TermGroup", func(t *testing.T) {
265 tg := &TermGroup{
266 Operands: []Node{&Term{Key: "A", Match: MatchEqual}},
267 Relation: AndRelation,
268 }
269 rw := Rewrite{Editor: "editor", Original: "orig"}
270 AppendRewrite(tg, rw)
271 assert.Equal(t, []Rewrite{rw}, tg.Rewrites)
272 })
273
274 t.Run("Append to Token", func(t *testing.T) {
275 tok := &Token{Wrap: &Term{Key: "X", Match: MatchEqual}}
276 rw := Rewrite{Editor: "ed"}
277 AppendRewrite(tok, rw)
278 assert.Equal(t, []Rewrite{rw}, tok.Rewrites)
279 })
280
281 t.Run("Append to non-Rewriteable is no-op", func(t *testing.T) {
282 catchall := &CatchallNode{NodeType: "koral:span"}
283 rw := Rewrite{Editor: "test"}
284 AppendRewrite(catchall, rw)
285 // CatchallNode doesn't implement Rewriteable, so nothing happens
286 })
287
288 t.Run("Append to nil is no-op", func(t *testing.T) {
289 assert.NotPanics(t, func() {
290 AppendRewrite(nil, Rewrite{Editor: "x"})
291 })
292 })
293}
294
295func TestStripRewrites(t *testing.T) {
296 t.Run("Strips from Term", func(t *testing.T) {
297 term := &Term{
298 Key: "DET",
299 Match: MatchEqual,
300 Rewrites: []Rewrite{{Editor: "a"}, {Editor: "b"}},
301 }
302 StripRewrites(term)
303 assert.Nil(t, term.Rewrites)
304 })
305
306 t.Run("Strips from Token and its Wrap", func(t *testing.T) {
307 tok := &Token{
308 Wrap: &Term{
309 Key: "DET",
310 Match: MatchEqual,
311 Rewrites: []Rewrite{{Editor: "inner"}},
312 },
313 Rewrites: []Rewrite{{Editor: "outer"}},
314 }
315 StripRewrites(tok)
316 assert.Nil(t, tok.Rewrites)
317 assert.Nil(t, tok.Wrap.(*Term).Rewrites)
318 })
319
320 t.Run("Strips from TermGroup and all operands", func(t *testing.T) {
321 tg := &TermGroup{
322 Operands: []Node{
323 &Term{Key: "A", Match: MatchEqual, Rewrites: []Rewrite{{Editor: "e1"}}},
324 &Term{Key: "B", Match: MatchEqual, Rewrites: []Rewrite{{Editor: "e2"}}},
325 },
326 Relation: AndRelation,
327 Rewrites: []Rewrite{{Editor: "group"}},
328 }
329 StripRewrites(tg)
330 assert.Nil(t, tg.Rewrites)
331 assert.Nil(t, tg.Operands[0].(*Term).Rewrites)
332 assert.Nil(t, tg.Operands[1].(*Term).Rewrites)
333 })
334
335 t.Run("Strips recursively from CatchallNode", func(t *testing.T) {
336 catchall := &CatchallNode{
337 NodeType: "koral:group",
338 Wrap: &Term{
339 Key: "W",
340 Match: MatchEqual,
341 Rewrites: []Rewrite{{Editor: "wrap-ed"}},
342 },
343 Operands: []Node{
344 &Token{
345 Wrap: &Term{Key: "X", Match: MatchEqual, Rewrites: []Rewrite{{Editor: "deep"}}},
346 Rewrites: []Rewrite{{Editor: "tok"}},
347 },
348 },
349 }
350 StripRewrites(catchall)
351 assert.Nil(t, catchall.Wrap.(*Term).Rewrites)
352 tok := catchall.Operands[0].(*Token)
353 assert.Nil(t, tok.Rewrites)
354 assert.Nil(t, tok.Wrap.(*Term).Rewrites)
355 })
356
357 t.Run("Nil node does not panic", func(t *testing.T) {
358 assert.NotPanics(t, func() {
359 StripRewrites(nil)
360 })
361 })
362
363 t.Run("Already empty rewrites stays nil", func(t *testing.T) {
364 term := &Term{Key: "DET", Match: MatchEqual}
365 StripRewrites(term)
366 assert.Nil(t, term.Rewrites)
367 })
368}
369
Akron8f1970f2025-05-30 12:52:03 +0200370func TestRewriteMarshalJSON(t *testing.T) {
371 // Test that marshaling works correctly and maintains the modern field names
372 rewrite := Rewrite{
373 Editor: "termMapper",
374 Operation: "operation:mapping",
375 Scope: "foundry",
376 Src: "source-value",
377 Comment: "Test comment",
378 Original: "original-value",
379 }
380
381 data, err := json.Marshal(rewrite)
382 require.NoError(t, err)
383
384 // Parse back to verify structure
385 var result map[string]any
386 err = json.Unmarshal(data, &result)
387 require.NoError(t, err)
388
389 assert.Equal(t, "termMapper", result["editor"])
390 assert.Equal(t, "operation:mapping", result["operation"])
391 assert.Equal(t, "foundry", result["scope"])
392 assert.Equal(t, "source-value", result["src"])
393 assert.Equal(t, "Test comment", result["_comment"])
394 assert.Equal(t, "original-value", result["original"])
395
396 // Ensure legacy fields are not present in output
397 assert.NotContains(t, result, "source")
398 assert.NotContains(t, result, "origin")
399}