blob: 0d0e5cc101acddc4790e3ba9a6fb4ec36cdf3973 [file] [log] [blame]
Akron57ee5582025-05-21 15:25:13 +02001package config
2
3import (
4 "os"
5 "testing"
6
Akronc79d87e2025-05-26 15:03:27 +02007 "github.com/KorAP/KoralPipe-TermMapper2/ast"
Akron57ee5582025-05-21 15:25:13 +02008 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10)
11
12func TestLoadConfig(t *testing.T) {
13 // Create a temporary YAML file
14 content := `
15- id: opennlp-mapper
16 foundryA: opennlp
17 layerA: p
18 foundryB: upos
19 layerB: p
20 mappings:
21 - "[PIDAT] <> [opennlp/p=PIDAT & opennlp/p=AdjType:Pdt]"
22 - "[PAV] <> [ADV & PronType:Dem]"
23
24- id: simple-mapper
25 mappings:
26 - "[A] <> [B]"
27`
28 tmpfile, err := os.CreateTemp("", "config-*.yaml")
29 require.NoError(t, err)
30 defer os.Remove(tmpfile.Name())
31
32 _, err = tmpfile.WriteString(content)
33 require.NoError(t, err)
34 err = tmpfile.Close()
35 require.NoError(t, err)
36
37 // Test loading the configuration
38 config, err := LoadConfig(tmpfile.Name())
39 require.NoError(t, err)
40
41 // Verify the configuration
42 require.Len(t, config.Lists, 2)
43
44 // Check first mapping list
45 list1 := config.Lists[0]
46 assert.Equal(t, "opennlp-mapper", list1.ID)
47 assert.Equal(t, "opennlp", list1.FoundryA)
48 assert.Equal(t, "p", list1.LayerA)
49 assert.Equal(t, "upos", list1.FoundryB)
50 assert.Equal(t, "p", list1.LayerB)
51 require.Len(t, list1.Mappings, 2)
52 assert.Equal(t, "[PIDAT] <> [opennlp/p=PIDAT & opennlp/p=AdjType:Pdt]", string(list1.Mappings[0]))
53 assert.Equal(t, "[PAV] <> [ADV & PronType:Dem]", string(list1.Mappings[1]))
54
55 // Check second mapping list
56 list2 := config.Lists[1]
57 assert.Equal(t, "simple-mapper", list2.ID)
58 assert.Empty(t, list2.FoundryA)
59 assert.Empty(t, list2.LayerA)
60 assert.Empty(t, list2.FoundryB)
61 assert.Empty(t, list2.LayerB)
62 require.Len(t, list2.Mappings, 1)
63 assert.Equal(t, "[A] <> [B]", string(list2.Mappings[0]))
64}
65
66func TestParseMappings(t *testing.T) {
67 list := &MappingList{
68 ID: "test-mapper",
69 FoundryA: "opennlp",
70 LayerA: "p",
71 FoundryB: "upos",
72 LayerB: "p",
73 Mappings: []MappingRule{
74 "[PIDAT] <> [opennlp/p=PIDAT & opennlp/p=AdjType:Pdt]",
75 },
76 }
77
78 results, err := list.ParseMappings()
79 require.NoError(t, err)
80 require.Len(t, results, 1)
81
82 // Check the parsed upper pattern
83 upper := results[0].Upper
84 require.NotNil(t, upper)
85 require.IsType(t, &ast.Token{}, upper)
86 upperTerm := upper.Wrap.(*ast.Term)
87 assert.Equal(t, "opennlp", upperTerm.Foundry)
88 assert.Equal(t, "p", upperTerm.Layer)
89 assert.Equal(t, "PIDAT", upperTerm.Key)
90
91 // Check the parsed lower pattern
92 lower := results[0].Lower
93 require.NotNil(t, lower)
94 require.IsType(t, &ast.Token{}, lower)
95 lowerGroup := lower.Wrap.(*ast.TermGroup)
96 require.Len(t, lowerGroup.Operands, 2)
97 assert.Equal(t, ast.AndRelation, lowerGroup.Relation)
98
99 // Check first operand
100 term1 := lowerGroup.Operands[0].(*ast.Term)
101 assert.Equal(t, "opennlp", term1.Foundry)
102 assert.Equal(t, "p", term1.Layer)
103 assert.Equal(t, "PIDAT", term1.Key)
104
105 // Check second operand
106 term2 := lowerGroup.Operands[1].(*ast.Term)
107 assert.Equal(t, "opennlp", term2.Foundry)
108 assert.Equal(t, "p", term2.Layer)
109 assert.Equal(t, "AdjType", term2.Key)
110 assert.Equal(t, "Pdt", term2.Value)
111}
112
113func TestLoadConfigValidation(t *testing.T) {
114 tests := []struct {
115 name string
116 content string
117 wantErr string
118 }{
119 {
120 name: "Missing ID",
121 content: `
122- foundryA: opennlp
123 mappings:
124 - "[A] <> [B]"
125`,
126 wantErr: "mapping list at index 0 is missing an ID",
127 },
128 {
129 name: "Empty mappings",
130 content: `
131- id: test
132 foundryA: opennlp
133 mappings: []
134`,
135 wantErr: "mapping list 'test' has no mapping rules",
136 },
137 {
138 name: "Empty rule",
139 content: `
140- id: test
141 mappings:
142 - ""
143`,
144 wantErr: "mapping list 'test' rule at index 0 is empty",
145 },
146 }
147
148 for _, tt := range tests {
149 t.Run(tt.name, func(t *testing.T) {
150 tmpfile, err := os.CreateTemp("", "config-*.yaml")
151 require.NoError(t, err)
152 defer os.Remove(tmpfile.Name())
153
154 _, err = tmpfile.WriteString(tt.content)
155 require.NoError(t, err)
156 err = tmpfile.Close()
157 require.NoError(t, err)
158
159 _, err = LoadConfig(tmpfile.Name())
160 require.Error(t, err)
161 assert.Contains(t, err.Error(), tt.wantErr)
162 })
163 }
164}
Akrona5d88142025-05-22 14:42:09 +0200165
166func TestLoadConfigEdgeCases(t *testing.T) {
167 tests := []struct {
168 name string
169 content string
170 wantErr string
171 }{
172 {
173 name: "Duplicate mapping list IDs",
174 content: `
175- id: test
176 mappings:
177 - "[A] <> [B]"
178- id: test
179 mappings:
180 - "[C] <> [D]"`,
181 wantErr: "duplicate mapping list ID found: test",
182 },
183 {
184 name: "Invalid YAML syntax",
185 content: `
186- id: test
187 mappings:
188 - [A] <> [B] # Unquoted special characters
189`,
190 wantErr: "yaml",
191 },
192 {
193 name: "Empty file",
194 content: "",
195 wantErr: "EOF",
196 },
197 {
198 name: "Non-list YAML",
199 content: `
200id: test
201mappings:
202 - "[A] <> [B]"`,
203 wantErr: "cannot unmarshal",
204 },
205 {
206 name: "Missing required fields",
207 content: `
208- mappings:
209 - "[A] <> [B]"
210- id: test2
211 foundryA: opennlp`,
212 wantErr: "missing an ID",
213 },
214 {
215 name: "Empty mappings list",
216 content: `
217- id: test
218 foundryA: opennlp
219 mappings: []`,
220 wantErr: "has no mapping rules",
221 },
222 {
223 name: "Null values in optional fields",
224 content: `
225- id: test
226 foundryA: null
227 layerA: null
228 foundryB: null
229 layerB: null
230 mappings:
231 - "[A] <> [B]"`,
232 wantErr: "",
233 },
234 {
235 name: "Special characters in IDs",
236 content: `
237- id: "test/special@chars#1"
238 mappings:
239 - "[A] <> [B]"`,
240 wantErr: "",
241 },
242 {
243 name: "Unicode characters in mappings",
244 content: `
245- id: test
246 mappings:
247 - "[ß] <> [ss]"
248 - "[é] <> [e]"`,
249 wantErr: "",
250 },
251 }
252
253 for _, tt := range tests {
254 t.Run(tt.name, func(t *testing.T) {
255 tmpfile, err := os.CreateTemp("", "config-*.yaml")
256 require.NoError(t, err)
257 defer os.Remove(tmpfile.Name())
258
259 _, err = tmpfile.WriteString(tt.content)
260 require.NoError(t, err)
261 err = tmpfile.Close()
262 require.NoError(t, err)
263
264 config, err := LoadConfig(tmpfile.Name())
265 if tt.wantErr != "" {
266 require.Error(t, err)
267 assert.Contains(t, err.Error(), tt.wantErr)
268 return
269 }
270 require.NoError(t, err)
271 require.NotNil(t, config)
272 })
273 }
274}
275
276func TestParseMappingsEdgeCases(t *testing.T) {
277 tests := []struct {
278 name string
279 list *MappingList
280 wantErr bool
281 errCheck func(t *testing.T, err error)
282 }{
283 {
284 name: "Empty mapping rule",
285 list: &MappingList{
286 ID: "test",
287 Mappings: []MappingRule{""},
288 },
289 wantErr: true,
290 errCheck: func(t *testing.T, err error) {
291 assert.Contains(t, err.Error(), "empty")
292 },
293 },
294 {
295 name: "Invalid mapping syntax",
296 list: &MappingList{
297 ID: "test",
298 Mappings: []MappingRule{"[A] -> [B]"},
299 },
300 wantErr: true,
301 errCheck: func(t *testing.T, err error) {
302 assert.Contains(t, err.Error(), "failed to parse")
303 },
304 },
305 {
306 name: "Missing brackets",
307 list: &MappingList{
308 ID: "test",
309 Mappings: []MappingRule{"A <> B"},
310 },
311 wantErr: true,
312 errCheck: func(t *testing.T, err error) {
313 assert.Contains(t, err.Error(), "failed to parse")
314 },
315 },
316 {
317 name: "Complex nested expressions",
318 list: &MappingList{
319 ID: "test",
320 Mappings: []MappingRule{
321 "[A & (B | C) & (D | (E & F))] <> [X & (Y | Z)]",
322 },
323 },
324 wantErr: false,
325 },
326 {
327 name: "Multiple foundry/layer combinations",
328 list: &MappingList{
329 ID: "test",
330 Mappings: []MappingRule{
331 "[foundry1/layer1=A & foundry2/layer2=B] <> [foundry3/layer3=C]",
332 },
333 },
334 wantErr: false,
335 },
336 {
337 name: "Default foundry/layer override",
338 list: &MappingList{
339 ID: "test",
340 FoundryA: "defaultFoundry",
341 LayerA: "defaultLayer",
342 Mappings: []MappingRule{
343 "[A] <> [B]", // Should use defaults
344 },
345 },
346 wantErr: false,
347 },
348 }
349
350 for _, tt := range tests {
351 t.Run(tt.name, func(t *testing.T) {
352 results, err := tt.list.ParseMappings()
353 if tt.wantErr {
354 require.Error(t, err)
355 if tt.errCheck != nil {
356 tt.errCheck(t, err)
357 }
358 return
359 }
360 require.NoError(t, err)
361 require.NotNil(t, results)
362 })
363 }
364}