blob: 329712c409cb16314d695006fc85b00f727ccece [file] [log] [blame]
Akron57ee5582025-05-21 15:25:13 +02001package config
2
3import (
4 "os"
5 "testing"
6
Akronfa55bb22025-05-26 15:10:42 +02007 "github.com/KorAP/KoralPipe-TermMapper/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]"`,
Akron813780f2025-06-05 15:44:28 +0200203 wantErr: "no mapping lists found",
Akrona5d88142025-05-22 14:42:09 +0200204 },
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}
Akroncc25e932025-06-02 19:39:43 +0200365
366func TestUserProvidedMappingRules(t *testing.T) {
367 // Test the exact YAML mapping rules provided by the user
368 content := `
369- id: stts-ud
370 foundryA: opennlp
371 layerA: p
372 foundryB: upos
373 layerB: p
374 mappings:
375 - "[$\\(] <> [PUNCT & PunctType=Brck]"
376 - "[$,] <> [PUNCT & PunctType=Comm]"
377 - "[$.] <> [PUNCT & PunctType=Peri]"
378 - "[ADJA] <> [ADJ]"
379 - "[ADJD] <> [ADJ & Variant=Short]"
380 - "[ADV] <> [ADV]"
381`
382 tmpfile, err := os.CreateTemp("", "user-config-*.yaml")
383 require.NoError(t, err)
384 defer os.Remove(tmpfile.Name())
385
386 _, err = tmpfile.WriteString(content)
387 require.NoError(t, err)
388 err = tmpfile.Close()
389 require.NoError(t, err)
390
391 // Test loading the configuration
392 config, err := LoadConfig(tmpfile.Name())
393 require.NoError(t, err)
394
395 // Verify the configuration loaded correctly
396 require.Len(t, config.Lists, 1)
397 list := config.Lists[0]
398 assert.Equal(t, "stts-ud", list.ID)
399 assert.Equal(t, "opennlp", list.FoundryA)
400 assert.Equal(t, "p", list.LayerA)
401 assert.Equal(t, "upos", list.FoundryB)
402 assert.Equal(t, "p", list.LayerB)
403 require.Len(t, list.Mappings, 6)
404
405 // First, test individual mappings to isolate the issue
406 t.Run("parenthesis mapping", func(t *testing.T) {
407 singleRule := &MappingList{
408 ID: "test-paren",
409 FoundryA: "opennlp",
410 LayerA: "p",
411 FoundryB: "upos",
412 LayerB: "p",
413 Mappings: []MappingRule{"[$\\(] <> [PUNCT & PunctType=Brck]"},
414 }
415 results, err := singleRule.ParseMappings()
416 require.NoError(t, err)
417 require.Len(t, results, 1)
418
419 upperTerm := results[0].Upper.Wrap.(*ast.Term)
420 assert.Equal(t, "$(", upperTerm.Key)
421 })
422
423 t.Run("comma mapping", func(t *testing.T) {
424 singleRule := &MappingList{
425 ID: "test-comma",
426 FoundryA: "opennlp",
427 LayerA: "p",
428 FoundryB: "upos",
429 LayerB: "p",
430 Mappings: []MappingRule{"[$,] <> [PUNCT & PunctType=Comm]"},
431 }
432 results, err := singleRule.ParseMappings()
433 require.NoError(t, err)
434 require.Len(t, results, 1)
435
436 upperTerm := results[0].Upper.Wrap.(*ast.Term)
437 assert.Equal(t, "$,", upperTerm.Key)
438 })
439
440 t.Run("period mapping", func(t *testing.T) {
441 singleRule := &MappingList{
442 ID: "test-period",
443 FoundryA: "opennlp",
444 LayerA: "p",
445 FoundryB: "upos",
446 LayerB: "p",
447 Mappings: []MappingRule{"[$.] <> [PUNCT & PunctType=Peri]"},
448 }
449 results, err := singleRule.ParseMappings()
450 require.NoError(t, err)
451 require.Len(t, results, 1)
452
453 upperTerm := results[0].Upper.Wrap.(*ast.Term)
454 assert.Equal(t, "$.", upperTerm.Key)
455 })
456
457 // Test that all mapping rules can be parsed successfully
458 results, err := list.ParseMappings()
459 require.NoError(t, err)
460 require.Len(t, results, 6)
461
462 // Verify specific parsing of the special character mapping
463 // The first mapping "[$\\(] <> [PUNCT & PunctType=Brck]" should parse correctly
464 firstMapping := results[0]
465 require.NotNil(t, firstMapping.Upper)
466 upperTerm := firstMapping.Upper.Wrap.(*ast.Term)
467 assert.Equal(t, "$(", upperTerm.Key) // The actual parsed key should be "$("
468 assert.Equal(t, "opennlp", upperTerm.Foundry)
469 assert.Equal(t, "p", upperTerm.Layer)
470
471 require.NotNil(t, firstMapping.Lower)
472 lowerGroup := firstMapping.Lower.Wrap.(*ast.TermGroup)
473 require.Len(t, lowerGroup.Operands, 2)
474 assert.Equal(t, ast.AndRelation, lowerGroup.Relation)
475
476 // Check the PUNCT term
477 punctTerm := lowerGroup.Operands[0].(*ast.Term)
478 assert.Equal(t, "PUNCT", punctTerm.Key)
479 assert.Equal(t, "upos", punctTerm.Foundry)
480 assert.Equal(t, "p", punctTerm.Layer)
481
482 // Check the PunctType term
483 punctTypeTerm := lowerGroup.Operands[1].(*ast.Term)
484 assert.Equal(t, "PunctType", punctTypeTerm.Layer)
485 assert.Equal(t, "Brck", punctTypeTerm.Key)
486 assert.Equal(t, "upos", punctTypeTerm.Foundry)
487
488 // Verify the comma mapping as well
489 secondMapping := results[1]
490 upperTerm2 := secondMapping.Upper.Wrap.(*ast.Term)
491 assert.Equal(t, "$,", upperTerm2.Key)
492
493 // Verify the period mapping
494 thirdMapping := results[2]
495 upperTerm3 := thirdMapping.Upper.Wrap.(*ast.Term)
496 assert.Equal(t, "$.", upperTerm3.Key)
497
498 // Verify basic mappings without special characters
499 fourthMapping := results[3]
500 upperTerm4 := fourthMapping.Upper.Wrap.(*ast.Term)
501 assert.Equal(t, "ADJA", upperTerm4.Key)
502 lowerTerm4 := fourthMapping.Lower.Wrap.(*ast.Term)
503 assert.Equal(t, "ADJ", lowerTerm4.Key)
504}
505
Akron06d21f02025-06-04 14:36:07 +0200506func TestConfigWithSdkAndServer(t *testing.T) {
507 tests := []struct {
508 name string
509 content string
510 expectedSDK string
511 expectedServer string
512 wantErr bool
513 }{
514 {
515 name: "Configuration with SDK and Server values",
516 content: `
517sdk: "https://custom.example.com/sdk.js"
518server: "https://custom.example.com/"
519lists:
520- id: test-mapper
521 foundryA: opennlp
522 layerA: p
523 foundryB: upos
524 layerB: p
525 mappings:
526 - "[A] <> [B]"
527`,
528 expectedSDK: "https://custom.example.com/sdk.js",
529 expectedServer: "https://custom.example.com/",
530 wantErr: false,
531 },
532 {
533 name: "Configuration with only SDK value",
534 content: `
535sdk: "https://custom.example.com/sdk.js"
536lists:
537- id: test-mapper
538 mappings:
539 - "[A] <> [B]"
540`,
541 expectedSDK: "https://custom.example.com/sdk.js",
542 expectedServer: "https://korap.ids-mannheim.de/", // default applied
543 wantErr: false,
544 },
545 {
546 name: "Configuration with only Server value",
547 content: `
548server: "https://custom.example.com/"
549lists:
550- id: test-mapper
551 mappings:
552 - "[A] <> [B]"
553`,
554 expectedSDK: "https://korap.ids-mannheim.de/js/korap-plugin-latest.js", // default applied
555 expectedServer: "https://custom.example.com/",
556 wantErr: false,
557 },
558 {
559 name: "Configuration without SDK and Server (old format with defaults applied)",
560 content: `
561- id: test-mapper
562 mappings:
563 - "[A] <> [B]"
564`,
565 expectedSDK: "https://korap.ids-mannheim.de/js/korap-plugin-latest.js", // default applied
566 expectedServer: "https://korap.ids-mannheim.de/", // default applied
567 wantErr: false,
568 },
569 {
570 name: "Configuration with lists field explicitly",
571 content: `
572sdk: "https://custom.example.com/sdk.js"
573server: "https://custom.example.com/"
574lists:
575- id: test-mapper-1
576 mappings:
577 - "[A] <> [B]"
578- id: test-mapper-2
579 mappings:
580 - "[C] <> [D]"
581`,
582 expectedSDK: "https://custom.example.com/sdk.js",
583 expectedServer: "https://custom.example.com/",
584 wantErr: false,
585 },
586 }
587
588 for _, tt := range tests {
589 t.Run(tt.name, func(t *testing.T) {
590 tmpfile, err := os.CreateTemp("", "config-*.yaml")
591 require.NoError(t, err)
592 defer os.Remove(tmpfile.Name())
593
594 _, err = tmpfile.WriteString(tt.content)
595 require.NoError(t, err)
596 err = tmpfile.Close()
597 require.NoError(t, err)
598
599 config, err := LoadConfig(tmpfile.Name())
600 if tt.wantErr {
601 require.Error(t, err)
602 return
603 }
604
605 require.NoError(t, err)
606 require.NotNil(t, config)
607
608 // Check SDK and Server values
609 assert.Equal(t, tt.expectedSDK, config.SDK)
610 assert.Equal(t, tt.expectedServer, config.Server)
611
612 // Ensure lists are still loaded correctly
613 require.Greater(t, len(config.Lists), 0)
614
615 // Verify first mapping list
616 firstList := config.Lists[0]
617 assert.NotEmpty(t, firstList.ID)
618 assert.Greater(t, len(firstList.Mappings), 0)
619 })
620 }
621}
Akrone1cff7c2025-06-04 18:43:32 +0200622
623func TestLoadFromSources(t *testing.T) {
624 // Create main config file
625 mainConfigContent := `
626sdk: "https://custom.example.com/sdk.js"
627server: "https://custom.example.com/"
628lists:
629- id: main-mapper
630 mappings:
631 - "[A] <> [B]"
632`
633 mainConfigFile, err := os.CreateTemp("", "main-config-*.yaml")
634 require.NoError(t, err)
635 defer os.Remove(mainConfigFile.Name())
636
637 _, err = mainConfigFile.WriteString(mainConfigContent)
638 require.NoError(t, err)
639 err = mainConfigFile.Close()
640 require.NoError(t, err)
641
642 // Create individual mapping files
643 mappingFile1Content := `
644id: mapper-1
645foundryA: opennlp
646layerA: p
647mappings:
648 - "[C] <> [D]"
649`
650 mappingFile1, err := os.CreateTemp("", "mapping1-*.yaml")
651 require.NoError(t, err)
652 defer os.Remove(mappingFile1.Name())
653
654 _, err = mappingFile1.WriteString(mappingFile1Content)
655 require.NoError(t, err)
656 err = mappingFile1.Close()
657 require.NoError(t, err)
658
659 mappingFile2Content := `
660id: mapper-2
661foundryB: upos
662layerB: p
663mappings:
664 - "[E] <> [F]"
665`
666 mappingFile2, err := os.CreateTemp("", "mapping2-*.yaml")
667 require.NoError(t, err)
668 defer os.Remove(mappingFile2.Name())
669
670 _, err = mappingFile2.WriteString(mappingFile2Content)
671 require.NoError(t, err)
672 err = mappingFile2.Close()
673 require.NoError(t, err)
674
675 tests := []struct {
676 name string
677 configFile string
678 mappingFiles []string
679 wantErr bool
680 expectedIDs []string
681 }{
682 {
683 name: "Main config only",
684 configFile: mainConfigFile.Name(),
685 mappingFiles: []string{},
686 wantErr: false,
687 expectedIDs: []string{"main-mapper"},
688 },
689 {
690 name: "Mapping files only",
691 configFile: "",
692 mappingFiles: []string{mappingFile1.Name(), mappingFile2.Name()},
693 wantErr: false,
694 expectedIDs: []string{"mapper-1", "mapper-2"},
695 },
696 {
697 name: "Main config and mapping files",
698 configFile: mainConfigFile.Name(),
699 mappingFiles: []string{mappingFile1.Name(), mappingFile2.Name()},
700 wantErr: false,
701 expectedIDs: []string{"main-mapper", "mapper-1", "mapper-2"},
702 },
703 {
704 name: "No configuration sources",
705 configFile: "",
706 mappingFiles: []string{},
707 wantErr: true,
708 },
709 }
710
711 for _, tt := range tests {
712 t.Run(tt.name, func(t *testing.T) {
713 config, err := LoadFromSources(tt.configFile, tt.mappingFiles)
714 if tt.wantErr {
715 require.Error(t, err)
716 return
717 }
718
719 require.NoError(t, err)
720 require.NotNil(t, config)
721
722 // Check that all expected mapping IDs are present
723 require.Len(t, config.Lists, len(tt.expectedIDs))
724 actualIDs := make([]string, len(config.Lists))
725 for i, list := range config.Lists {
726 actualIDs[i] = list.ID
727 }
728 for _, expectedID := range tt.expectedIDs {
729 assert.Contains(t, actualIDs, expectedID)
730 }
731
732 // Check that SDK and Server are set (either from config or defaults)
733 assert.NotEmpty(t, config.SDK)
734 assert.NotEmpty(t, config.Server)
735 })
736 }
737}
738
739func TestLoadFromSourcesWithDefaults(t *testing.T) {
740 // Test that defaults are applied when loading only mapping files
741 mappingFileContent := `
742id: test-mapper
743mappings:
744 - "[A] <> [B]"
745`
746 mappingFile, err := os.CreateTemp("", "mapping-*.yaml")
747 require.NoError(t, err)
748 defer os.Remove(mappingFile.Name())
749
750 _, err = mappingFile.WriteString(mappingFileContent)
751 require.NoError(t, err)
752 err = mappingFile.Close()
753 require.NoError(t, err)
754
755 config, err := LoadFromSources("", []string{mappingFile.Name()})
756 require.NoError(t, err)
757
758 // Check that defaults are applied
759 assert.Equal(t, defaultSDK, config.SDK)
760 assert.Equal(t, defaultServer, config.Server)
761 require.Len(t, config.Lists, 1)
762 assert.Equal(t, "test-mapper", config.Lists[0].ID)
763}
764
765func TestLoadFromSourcesDuplicateIDs(t *testing.T) {
766 // Create config with duplicate IDs across sources
767 configContent := `
768lists:
769- id: duplicate-id
770 mappings:
771 - "[A] <> [B]"
772`
773 configFile, err := os.CreateTemp("", "config-*.yaml")
774 require.NoError(t, err)
775 defer os.Remove(configFile.Name())
776
777 _, err = configFile.WriteString(configContent)
778 require.NoError(t, err)
779 err = configFile.Close()
780 require.NoError(t, err)
781
782 mappingContent := `
783id: duplicate-id
784mappings:
785 - "[C] <> [D]"
786`
787 mappingFile, err := os.CreateTemp("", "mapping-*.yaml")
788 require.NoError(t, err)
789 defer os.Remove(mappingFile.Name())
790
791 _, err = mappingFile.WriteString(mappingContent)
792 require.NoError(t, err)
793 err = mappingFile.Close()
794 require.NoError(t, err)
795
796 _, err = LoadFromSources(configFile.Name(), []string{mappingFile.Name()})
797 require.Error(t, err)
798 assert.Contains(t, err.Error(), "duplicate mapping list ID found: duplicate-id")
799}
Akron813780f2025-06-05 15:44:28 +0200800
801func TestLoadFromSourcesConfigWithOnlyPort(t *testing.T) {
802 // Create config file with only port (no lists)
803 configContent := `
804port: 8080
805loglevel: debug
806`
807 configFile, err := os.CreateTemp("", "config-*.yaml")
808 require.NoError(t, err)
809 defer os.Remove(configFile.Name())
810
811 _, err = configFile.WriteString(configContent)
812 require.NoError(t, err)
813 err = configFile.Close()
814 require.NoError(t, err)
815
816 // Create mapping file
817 mappingContent := `
818id: test-mapper
819mappings:
820 - "[A] <> [B]"
821`
822 mappingFile, err := os.CreateTemp("", "mapping-*.yaml")
823 require.NoError(t, err)
824 defer os.Remove(mappingFile.Name())
825
826 _, err = mappingFile.WriteString(mappingContent)
827 require.NoError(t, err)
828 err = mappingFile.Close()
829 require.NoError(t, err)
830
831 // This should work: config file has only port, mapping file provides the mapping list
832 config, err := LoadFromSources(configFile.Name(), []string{mappingFile.Name()})
833 require.NoError(t, err)
834 require.NotNil(t, config)
835
836 // Check that port and log level from config file are preserved
837 assert.Equal(t, 8080, config.Port)
838 assert.Equal(t, "debug", config.LogLevel)
839
840 // Check that mapping from mapping file is loaded
841 require.Len(t, config.Lists, 1)
842 assert.Equal(t, "test-mapper", config.Lists[0].ID)
843
844 // Check that defaults are applied for other fields
845 assert.Equal(t, defaultSDK, config.SDK)
846 assert.Equal(t, defaultServer, config.Server)
847 assert.Equal(t, defaultServiceURL, config.ServiceURL)
848}