blob: 0d0e5cc101acddc4790e3ba9a6fb4ec36cdf3973 [file] [log] [blame]
package config
import (
"os"
"testing"
"github.com/KorAP/KoralPipe-TermMapper2/ast"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoadConfig(t *testing.T) {
// Create a temporary YAML file
content := `
- id: opennlp-mapper
foundryA: opennlp
layerA: p
foundryB: upos
layerB: p
mappings:
- "[PIDAT] <> [opennlp/p=PIDAT & opennlp/p=AdjType:Pdt]"
- "[PAV] <> [ADV & PronType:Dem]"
- id: simple-mapper
mappings:
- "[A] <> [B]"
`
tmpfile, err := os.CreateTemp("", "config-*.yaml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
_, err = tmpfile.WriteString(content)
require.NoError(t, err)
err = tmpfile.Close()
require.NoError(t, err)
// Test loading the configuration
config, err := LoadConfig(tmpfile.Name())
require.NoError(t, err)
// Verify the configuration
require.Len(t, config.Lists, 2)
// Check first mapping list
list1 := config.Lists[0]
assert.Equal(t, "opennlp-mapper", list1.ID)
assert.Equal(t, "opennlp", list1.FoundryA)
assert.Equal(t, "p", list1.LayerA)
assert.Equal(t, "upos", list1.FoundryB)
assert.Equal(t, "p", list1.LayerB)
require.Len(t, list1.Mappings, 2)
assert.Equal(t, "[PIDAT] <> [opennlp/p=PIDAT & opennlp/p=AdjType:Pdt]", string(list1.Mappings[0]))
assert.Equal(t, "[PAV] <> [ADV & PronType:Dem]", string(list1.Mappings[1]))
// Check second mapping list
list2 := config.Lists[1]
assert.Equal(t, "simple-mapper", list2.ID)
assert.Empty(t, list2.FoundryA)
assert.Empty(t, list2.LayerA)
assert.Empty(t, list2.FoundryB)
assert.Empty(t, list2.LayerB)
require.Len(t, list2.Mappings, 1)
assert.Equal(t, "[A] <> [B]", string(list2.Mappings[0]))
}
func TestParseMappings(t *testing.T) {
list := &MappingList{
ID: "test-mapper",
FoundryA: "opennlp",
LayerA: "p",
FoundryB: "upos",
LayerB: "p",
Mappings: []MappingRule{
"[PIDAT] <> [opennlp/p=PIDAT & opennlp/p=AdjType:Pdt]",
},
}
results, err := list.ParseMappings()
require.NoError(t, err)
require.Len(t, results, 1)
// Check the parsed upper pattern
upper := results[0].Upper
require.NotNil(t, upper)
require.IsType(t, &ast.Token{}, upper)
upperTerm := upper.Wrap.(*ast.Term)
assert.Equal(t, "opennlp", upperTerm.Foundry)
assert.Equal(t, "p", upperTerm.Layer)
assert.Equal(t, "PIDAT", upperTerm.Key)
// Check the parsed lower pattern
lower := results[0].Lower
require.NotNil(t, lower)
require.IsType(t, &ast.Token{}, lower)
lowerGroup := lower.Wrap.(*ast.TermGroup)
require.Len(t, lowerGroup.Operands, 2)
assert.Equal(t, ast.AndRelation, lowerGroup.Relation)
// Check first operand
term1 := lowerGroup.Operands[0].(*ast.Term)
assert.Equal(t, "opennlp", term1.Foundry)
assert.Equal(t, "p", term1.Layer)
assert.Equal(t, "PIDAT", term1.Key)
// Check second operand
term2 := lowerGroup.Operands[1].(*ast.Term)
assert.Equal(t, "opennlp", term2.Foundry)
assert.Equal(t, "p", term2.Layer)
assert.Equal(t, "AdjType", term2.Key)
assert.Equal(t, "Pdt", term2.Value)
}
func TestLoadConfigValidation(t *testing.T) {
tests := []struct {
name string
content string
wantErr string
}{
{
name: "Missing ID",
content: `
- foundryA: opennlp
mappings:
- "[A] <> [B]"
`,
wantErr: "mapping list at index 0 is missing an ID",
},
{
name: "Empty mappings",
content: `
- id: test
foundryA: opennlp
mappings: []
`,
wantErr: "mapping list 'test' has no mapping rules",
},
{
name: "Empty rule",
content: `
- id: test
mappings:
- ""
`,
wantErr: "mapping list 'test' rule at index 0 is empty",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpfile, err := os.CreateTemp("", "config-*.yaml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
_, err = tmpfile.WriteString(tt.content)
require.NoError(t, err)
err = tmpfile.Close()
require.NoError(t, err)
_, err = LoadConfig(tmpfile.Name())
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
}
func TestLoadConfigEdgeCases(t *testing.T) {
tests := []struct {
name string
content string
wantErr string
}{
{
name: "Duplicate mapping list IDs",
content: `
- id: test
mappings:
- "[A] <> [B]"
- id: test
mappings:
- "[C] <> [D]"`,
wantErr: "duplicate mapping list ID found: test",
},
{
name: "Invalid YAML syntax",
content: `
- id: test
mappings:
- [A] <> [B] # Unquoted special characters
`,
wantErr: "yaml",
},
{
name: "Empty file",
content: "",
wantErr: "EOF",
},
{
name: "Non-list YAML",
content: `
id: test
mappings:
- "[A] <> [B]"`,
wantErr: "cannot unmarshal",
},
{
name: "Missing required fields",
content: `
- mappings:
- "[A] <> [B]"
- id: test2
foundryA: opennlp`,
wantErr: "missing an ID",
},
{
name: "Empty mappings list",
content: `
- id: test
foundryA: opennlp
mappings: []`,
wantErr: "has no mapping rules",
},
{
name: "Null values in optional fields",
content: `
- id: test
foundryA: null
layerA: null
foundryB: null
layerB: null
mappings:
- "[A] <> [B]"`,
wantErr: "",
},
{
name: "Special characters in IDs",
content: `
- id: "test/special@chars#1"
mappings:
- "[A] <> [B]"`,
wantErr: "",
},
{
name: "Unicode characters in mappings",
content: `
- id: test
mappings:
- "[ß] <> [ss]"
- "[é] <> [e]"`,
wantErr: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpfile, err := os.CreateTemp("", "config-*.yaml")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
_, err = tmpfile.WriteString(tt.content)
require.NoError(t, err)
err = tmpfile.Close()
require.NoError(t, err)
config, err := LoadConfig(tmpfile.Name())
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
require.NoError(t, err)
require.NotNil(t, config)
})
}
}
func TestParseMappingsEdgeCases(t *testing.T) {
tests := []struct {
name string
list *MappingList
wantErr bool
errCheck func(t *testing.T, err error)
}{
{
name: "Empty mapping rule",
list: &MappingList{
ID: "test",
Mappings: []MappingRule{""},
},
wantErr: true,
errCheck: func(t *testing.T, err error) {
assert.Contains(t, err.Error(), "empty")
},
},
{
name: "Invalid mapping syntax",
list: &MappingList{
ID: "test",
Mappings: []MappingRule{"[A] -> [B]"},
},
wantErr: true,
errCheck: func(t *testing.T, err error) {
assert.Contains(t, err.Error(), "failed to parse")
},
},
{
name: "Missing brackets",
list: &MappingList{
ID: "test",
Mappings: []MappingRule{"A <> B"},
},
wantErr: true,
errCheck: func(t *testing.T, err error) {
assert.Contains(t, err.Error(), "failed to parse")
},
},
{
name: "Complex nested expressions",
list: &MappingList{
ID: "test",
Mappings: []MappingRule{
"[A & (B | C) & (D | (E & F))] <> [X & (Y | Z)]",
},
},
wantErr: false,
},
{
name: "Multiple foundry/layer combinations",
list: &MappingList{
ID: "test",
Mappings: []MappingRule{
"[foundry1/layer1=A & foundry2/layer2=B] <> [foundry3/layer3=C]",
},
},
wantErr: false,
},
{
name: "Default foundry/layer override",
list: &MappingList{
ID: "test",
FoundryA: "defaultFoundry",
LayerA: "defaultLayer",
Mappings: []MappingRule{
"[A] <> [B]", // Should use defaults
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
results, err := tt.list.ParseMappings()
if tt.wantErr {
require.Error(t, err)
if tt.errCheck != nil {
tt.errCheck(t, err)
}
return
}
require.NoError(t, err)
require.NotNil(t, results)
})
}
}