blob: 998d4377d898708b9f879a8de14cb8799e439cd5 [file] [log] [blame]
Akrone4f570d2026-02-20 08:18:06 +01001package mapper
2
3import (
4 "encoding/json"
5 "testing"
6
7 "github.com/KorAP/Koral-Mapper/config"
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10)
11
12// parseJSON is a test helper that unmarshals a JSON string.
13func parseJSON(t *testing.T, s string) any {
14 t.Helper()
15 var v any
16 require.NoError(t, json.Unmarshal([]byte(s), &v))
17 return v
18}
19
20func TestCascadeQueryTwoAnnotationMappings(t *testing.T) {
21 m, err := NewMapper([]config.MappingList{
22 {
23 ID: "ann-step1", FoundryA: "opennlp", LayerA: "p",
24 FoundryB: "opennlp", LayerB: "p",
25 Mappings: []config.MappingRule{`[PIDAT] <> [DET]`},
26 },
27 {
28 ID: "ann-step2", FoundryA: "opennlp", LayerA: "p",
29 FoundryB: "upos", LayerB: "p",
30 Mappings: []config.MappingRule{`[DET] <> [PRON]`},
31 },
32 })
33 require.NoError(t, err)
34
35 input := parseJSON(t, `{
36 "@type": "koral:token",
37 "wrap": {
38 "@type": "koral:term",
39 "foundry": "opennlp",
40 "key": "PIDAT",
41 "layer": "p",
42 "match": "match:eq"
43 }
44 }`)
45
46 result, err := m.CascadeQueryMappings(
47 []string{"ann-step1", "ann-step2"},
48 []MappingOptions{{Direction: AtoB}, {Direction: AtoB}},
49 input,
50 )
51 require.NoError(t, err)
52
53 expected := parseJSON(t, `{
54 "@type": "koral:token",
55 "wrap": {
56 "@type": "koral:term",
57 "foundry": "upos",
58 "key": "PRON",
59 "layer": "p",
60 "match": "match:eq"
61 }
62 }`)
63 assert.Equal(t, expected, result)
64}
65
66func TestCascadeQueryMixAnnotationAndCorpus(t *testing.T) {
67 m, err := NewMapper([]config.MappingList{
68 {
69 ID: "ann-mapper", FoundryA: "opennlp", LayerA: "p",
70 FoundryB: "upos", LayerB: "p",
71 Mappings: []config.MappingRule{`[PIDAT] <> [DET]`},
72 },
73 {
74 ID: "corpus-mapper",
75 Type: "corpus",
76 Mappings: []config.MappingRule{`textClass=novel <> genre=fiction`},
77 },
78 })
79 require.NoError(t, err)
80
81 input := parseJSON(t, `{
82 "query": {
83 "@type": "koral:token",
84 "wrap": {
85 "@type": "koral:term",
86 "foundry": "opennlp",
87 "key": "PIDAT",
88 "layer": "p",
89 "match": "match:eq"
90 }
91 },
92 "collection": {
93 "@type": "koral:doc",
94 "key": "textClass",
95 "value": "novel",
96 "match": "match:eq"
97 }
98 }`)
99
100 result, err := m.CascadeQueryMappings(
101 []string{"ann-mapper", "corpus-mapper"},
102 []MappingOptions{{Direction: AtoB}, {Direction: AtoB}},
103 input,
104 )
105 require.NoError(t, err)
106
107 resultMap := result.(map[string]any)
108
109 query := resultMap["query"].(map[string]any)
110 wrap := query["wrap"].(map[string]any)
111 assert.Equal(t, "DET", wrap["key"])
112 assert.Equal(t, "upos", wrap["foundry"])
113
114 collection := resultMap["collection"].(map[string]any)
115 assert.Equal(t, "genre", collection["key"])
116 assert.Equal(t, "fiction", collection["value"])
117}
118
119func TestCascadeQuerySingleElement(t *testing.T) {
120 m, err := NewMapper([]config.MappingList{{
121 ID: "single", FoundryA: "opennlp", LayerA: "p",
122 FoundryB: "upos", LayerB: "p",
123 Mappings: []config.MappingRule{`[PIDAT] <> [DET]`},
124 }})
125 require.NoError(t, err)
126
127 makeInput := func() any {
128 return parseJSON(t, `{
129 "@type": "koral:token",
130 "wrap": {
131 "@type": "koral:term",
132 "foundry": "opennlp",
133 "key": "PIDAT",
134 "layer": "p",
135 "match": "match:eq"
136 }
137 }`)
138 }
139
140 opts := MappingOptions{Direction: AtoB}
141
142 cascadeResult, err := m.CascadeQueryMappings(
143 []string{"single"}, []MappingOptions{opts}, makeInput(),
144 )
145 require.NoError(t, err)
146
147 directResult, err := m.ApplyQueryMappings("single", opts, makeInput())
148 require.NoError(t, err)
149
150 assert.Equal(t, directResult, cascadeResult)
151}
152
153func TestCascadeQueryEmptyList(t *testing.T) {
154 m, err := NewMapper([]config.MappingList{{
155 ID: "dummy", FoundryA: "x", LayerA: "y",
156 FoundryB: "a", LayerB: "b",
157 Mappings: []config.MappingRule{`[X] <> [Y]`},
158 }})
159 require.NoError(t, err)
160
161 input := parseJSON(t, `{
162 "@type": "koral:token",
163 "wrap": {"@type": "koral:term", "key": "Z"}
164 }`)
165
166 result, err := m.CascadeQueryMappings(nil, nil, input)
167 require.NoError(t, err)
168 assert.Equal(t, input, result)
169}
170
171func TestCascadeQueryUnknownID(t *testing.T) {
172 m, err := NewMapper([]config.MappingList{{
173 ID: "known", FoundryA: "x", LayerA: "y",
174 FoundryB: "a", LayerB: "b",
175 Mappings: []config.MappingRule{`[X] <> [Y]`},
176 }})
177 require.NoError(t, err)
178
179 input := parseJSON(t, `{
180 "@type": "koral:token",
181 "wrap": {"@type": "koral:term", "key": "X"}
182 }`)
183
184 _, err = m.CascadeQueryMappings(
185 []string{"known", "nonexistent"},
186 []MappingOptions{{Direction: AtoB}, {Direction: AtoB}},
187 input,
188 )
189 assert.Error(t, err)
190 assert.Contains(t, err.Error(), "nonexistent")
191}
192
193// --- Response cascade tests ---
194
195func TestCascadeResponseTwoCorpusMappings(t *testing.T) {
196 m, err := NewMapper([]config.MappingList{
197 {
198 ID: "corpus-step1", Type: "corpus",
199 Mappings: []config.MappingRule{`textClass=novel <> genre=fiction`},
200 },
201 {
202 ID: "corpus-step2", Type: "corpus",
203 Mappings: []config.MappingRule{`genre=fiction <> category=lit`},
204 },
205 })
206 require.NoError(t, err)
207
208 input := parseJSON(t, `{
209 "fields": [{
210 "@type": "koral:field",
211 "key": "textClass",
212 "value": "novel",
213 "type": "type:string"
214 }]
215 }`)
216
217 result, err := m.CascadeResponseMappings(
218 []string{"corpus-step1", "corpus-step2"},
219 []MappingOptions{{Direction: AtoB}, {Direction: AtoB}},
220 input,
221 )
222 require.NoError(t, err)
223
224 fields := result.(map[string]any)["fields"].([]any)
225 require.GreaterOrEqual(t, len(fields), 3)
226
227 assert.Equal(t, "textClass", fields[0].(map[string]any)["key"])
228
229 assert.Equal(t, "genre", fields[1].(map[string]any)["key"])
230 assert.Equal(t, "fiction", fields[1].(map[string]any)["value"])
231
232 assert.Equal(t, "category", fields[2].(map[string]any)["key"])
233 assert.Equal(t, "lit", fields[2].(map[string]any)["value"])
234}
235
236func TestCascadeResponseMixAnnotationAndCorpus(t *testing.T) {
237 m, err := NewMapper([]config.MappingList{
238 {
239 ID: "ann-resp", FoundryA: "opennlp", LayerA: "p",
240 FoundryB: "upos", LayerB: "p",
241 Mappings: []config.MappingRule{`[DET] <> [PRON]`},
242 },
243 {
244 ID: "corpus-resp",
245 Type: "corpus",
246 Mappings: []config.MappingRule{`textClass=novel <> genre=fiction`},
247 },
248 })
249 require.NoError(t, err)
250
251 input := parseJSON(t, `{
252 "snippet": "<span title=\"opennlp/p:DET\">Der</span>",
253 "fields": [{
254 "@type": "koral:field",
255 "key": "textClass",
256 "value": "novel",
257 "type": "type:string"
258 }]
259 }`)
260
261 result, err := m.CascadeResponseMappings(
262 []string{"ann-resp", "corpus-resp"},
263 []MappingOptions{{Direction: AtoB}, {Direction: AtoB}},
264 input,
265 )
266 require.NoError(t, err)
267
268 resultMap := result.(map[string]any)
269
270 snippet := resultMap["snippet"].(string)
271 assert.Contains(t, snippet, "opennlp/p:DET")
272 assert.Contains(t, snippet, "upos/p:PRON")
273
274 fields := resultMap["fields"].([]any)
275 require.GreaterOrEqual(t, len(fields), 2)
276 assert.Equal(t, "genre", fields[1].(map[string]any)["key"])
277}
278
279func TestCascadeResponseSingleElement(t *testing.T) {
280 m, err := NewMapper([]config.MappingList{{
281 ID: "corpus-single", Type: "corpus",
282 Mappings: []config.MappingRule{`textClass=novel <> genre=fiction`},
283 }})
284 require.NoError(t, err)
285
286 makeInput := func() any {
287 return parseJSON(t, `{
288 "fields": [{
289 "@type": "koral:field",
290 "key": "textClass",
291 "value": "novel",
292 "type": "type:string"
293 }]
294 }`)
295 }
296
297 opts := MappingOptions{Direction: AtoB}
298
299 cascadeResult, err := m.CascadeResponseMappings(
300 []string{"corpus-single"}, []MappingOptions{opts}, makeInput(),
301 )
302 require.NoError(t, err)
303
304 directResult, err := m.ApplyResponseMappings("corpus-single", opts, makeInput())
305 require.NoError(t, err)
306
307 assert.Equal(t, directResult, cascadeResult)
308}
309
310func TestCascadeResponseEmptyList(t *testing.T) {
311 m, err := NewMapper([]config.MappingList{{
312 ID: "dummy", Type: "corpus",
313 Mappings: []config.MappingRule{`x=y <> a=b`},
314 }})
315 require.NoError(t, err)
316
317 input := parseJSON(t, `{"fields": []}`)
318
319 result, err := m.CascadeResponseMappings(nil, nil, input)
320 require.NoError(t, err)
321 assert.Equal(t, input, result)
322}
323
324func TestCascadeResponseUnknownID(t *testing.T) {
325 m, err := NewMapper([]config.MappingList{{
326 ID: "known", Type: "corpus",
327 Mappings: []config.MappingRule{`x=y <> a=b`},
328 }})
329 require.NoError(t, err)
330
331 _, err = m.CascadeResponseMappings(
332 []string{"nonexistent"},
333 []MappingOptions{{Direction: AtoB}},
334 parseJSON(t, `{"fields": []}`),
335 )
336 assert.Error(t, err)
337 assert.Contains(t, err.Error(), "nonexistent")
338}