blob: 4f80eef1630a98bbc57ac34ed279bb1454df6217 [file] [log] [blame]
Akron2cbdab52025-05-23 17:57:10 +02001package main
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "net/http/httptest"
9 "net/url"
10 "os"
11 "path/filepath"
12 "testing"
13
14 "github.com/KorAP/KoralPipe-TermMapper2/pkg/mapper"
15 "github.com/gofiber/fiber/v2"
16)
17
18// FuzzInput represents the input data for the fuzzer
19type FuzzInput struct {
20 MapID string
21 Direction string
22 FoundryA string
23 FoundryB string
24 LayerA string
25 LayerB string
26 Body []byte
27}
28
29func FuzzTransformEndpoint(f *testing.F) {
30 // Create a temporary config file with valid mappings
31 tmpDir := f.TempDir()
32 configFile := filepath.Join(tmpDir, "test-config.yaml")
33
34 configContent := `- id: test-mapper
35 foundryA: opennlp
36 layerA: p
37 foundryB: upos
38 layerB: p
39 mappings:
40 - "[PIDAT] <> [opennlp/p=PIDAT & opennlp/p=AdjType:Pdt]"
41 - "[DET] <> [opennlp/p=DET]"`
42
43 err := os.WriteFile(configFile, []byte(configContent), 0644)
44 if err != nil {
45 f.Fatal(err)
46 }
47
48 // Create mapper
49 m, err := mapper.NewMapper(configFile)
50 if err != nil {
51 f.Fatal(err)
52 }
53
54 // Create fiber app
55 app := fiber.New(fiber.Config{
56 DisableStartupMessage: true,
57 ErrorHandler: func(c *fiber.Ctx, err error) error {
58 // Ensure we always return a valid JSON response even for panic cases
59 return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
60 "error": "internal server error",
61 })
62 },
63 })
64 setupRoutes(app, m)
65
66 // Add seed corpus
67 f.Add("test-mapper", "atob", "", "", "", "", []byte(`{"@type": "koral:token"}`)) // Valid minimal input
68 f.Add("test-mapper", "btoa", "custom", "", "", "", []byte(`{"@type": "koral:token"}`)) // Valid with foundry override
69 f.Add("", "", "", "", "", "", []byte(`{}`)) // Empty parameters
70 f.Add("nonexistent", "invalid", "!@#$", "%^&*", "()", "[]", []byte(`invalid json`)) // Invalid everything
71 f.Add("test-mapper", "atob", "", "", "", "", []byte(`{"@type": "koral:token", "wrap": null}`)) // Valid JSON, invalid structure
72 f.Add("test-mapper", "atob", "", "", "", "", []byte(`{"@type": "koral:token", "wrap": {"@type": "unknown"}}`)) // Unknown type
73 f.Add("test-mapper", "atob", "", "", "", "", []byte(`{"@type": "koral:token", "wrap": {"@type": "koral:term"}}`)) // Missing required fields
74
75 f.Fuzz(func(t *testing.T, mapID, dir, foundryA, foundryB, layerA, layerB string, body []byte) {
76 // Build URL with query parameters
77 params := url.Values{}
78 if dir != "" {
79 params.Set("dir", dir)
80 }
81 if foundryA != "" {
82 params.Set("foundryA", foundryA)
83 }
84 if foundryB != "" {
85 params.Set("foundryB", foundryB)
86 }
87 if layerA != "" {
88 params.Set("layerA", layerA)
89 }
90 if layerB != "" {
91 params.Set("layerB", layerB)
92 }
93
94 url := fmt.Sprintf("/%s/query", url.PathEscape(mapID))
95 if len(params) > 0 {
96 url += "?" + params.Encode()
97 }
98
99 // Make request
100 req := httptest.NewRequest(http.MethodPost, url, bytes.NewReader(body))
101 req.Header.Set("Content-Type", "application/json")
102 resp, err := app.Test(req)
103 if err != nil {
104 t.Fatal(err)
105 }
106 defer resp.Body.Close()
107
108 // Verify that we always get a valid response
109 if resp.StatusCode != http.StatusOK &&
110 resp.StatusCode != http.StatusBadRequest &&
111 resp.StatusCode != http.StatusInternalServerError {
112 t.Errorf("unexpected status code: %d", resp.StatusCode)
113 }
114
115 // Verify that the response is valid JSON
116 var result map[string]interface{}
117 if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
118 t.Errorf("invalid JSON response: %v", err)
119 }
120
121 // For error responses, verify that we have an error message
122 if resp.StatusCode != http.StatusOK {
123 if errMsg, ok := result["error"].(string); !ok || errMsg == "" {
124 t.Error("error response missing error message")
125 }
126 }
127 })
128}