Add fuzzing tests
diff --git a/cmd/termmapper/fuzz_test.go b/cmd/termmapper/fuzz_test.go
new file mode 100644
index 0000000..4f80eef
--- /dev/null
+++ b/cmd/termmapper/fuzz_test.go
@@ -0,0 +1,128 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/KorAP/KoralPipe-TermMapper2/pkg/mapper"
+ "github.com/gofiber/fiber/v2"
+)
+
+// FuzzInput represents the input data for the fuzzer
+type FuzzInput struct {
+ MapID string
+ Direction string
+ FoundryA string
+ FoundryB string
+ LayerA string
+ LayerB string
+ Body []byte
+}
+
+func FuzzTransformEndpoint(f *testing.F) {
+ // Create a temporary config file with valid mappings
+ tmpDir := f.TempDir()
+ configFile := filepath.Join(tmpDir, "test-config.yaml")
+
+ configContent := `- id: test-mapper
+ foundryA: opennlp
+ layerA: p
+ foundryB: upos
+ layerB: p
+ mappings:
+ - "[PIDAT] <> [opennlp/p=PIDAT & opennlp/p=AdjType:Pdt]"
+ - "[DET] <> [opennlp/p=DET]"`
+
+ err := os.WriteFile(configFile, []byte(configContent), 0644)
+ if err != nil {
+ f.Fatal(err)
+ }
+
+ // Create mapper
+ m, err := mapper.NewMapper(configFile)
+ if err != nil {
+ f.Fatal(err)
+ }
+
+ // Create fiber app
+ app := fiber.New(fiber.Config{
+ DisableStartupMessage: true,
+ ErrorHandler: func(c *fiber.Ctx, err error) error {
+ // Ensure we always return a valid JSON response even for panic cases
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "internal server error",
+ })
+ },
+ })
+ setupRoutes(app, m)
+
+ // Add seed corpus
+ f.Add("test-mapper", "atob", "", "", "", "", []byte(`{"@type": "koral:token"}`)) // Valid minimal input
+ f.Add("test-mapper", "btoa", "custom", "", "", "", []byte(`{"@type": "koral:token"}`)) // Valid with foundry override
+ f.Add("", "", "", "", "", "", []byte(`{}`)) // Empty parameters
+ f.Add("nonexistent", "invalid", "!@#$", "%^&*", "()", "[]", []byte(`invalid json`)) // Invalid everything
+ f.Add("test-mapper", "atob", "", "", "", "", []byte(`{"@type": "koral:token", "wrap": null}`)) // Valid JSON, invalid structure
+ f.Add("test-mapper", "atob", "", "", "", "", []byte(`{"@type": "koral:token", "wrap": {"@type": "unknown"}}`)) // Unknown type
+ f.Add("test-mapper", "atob", "", "", "", "", []byte(`{"@type": "koral:token", "wrap": {"@type": "koral:term"}}`)) // Missing required fields
+
+ f.Fuzz(func(t *testing.T, mapID, dir, foundryA, foundryB, layerA, layerB string, body []byte) {
+ // Build URL with query parameters
+ params := url.Values{}
+ if dir != "" {
+ params.Set("dir", dir)
+ }
+ if foundryA != "" {
+ params.Set("foundryA", foundryA)
+ }
+ if foundryB != "" {
+ params.Set("foundryB", foundryB)
+ }
+ if layerA != "" {
+ params.Set("layerA", layerA)
+ }
+ if layerB != "" {
+ params.Set("layerB", layerB)
+ }
+
+ url := fmt.Sprintf("/%s/query", url.PathEscape(mapID))
+ if len(params) > 0 {
+ url += "?" + params.Encode()
+ }
+
+ // Make request
+ req := httptest.NewRequest(http.MethodPost, url, bytes.NewReader(body))
+ req.Header.Set("Content-Type", "application/json")
+ resp, err := app.Test(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer resp.Body.Close()
+
+ // Verify that we always get a valid response
+ if resp.StatusCode != http.StatusOK &&
+ resp.StatusCode != http.StatusBadRequest &&
+ resp.StatusCode != http.StatusInternalServerError {
+ t.Errorf("unexpected status code: %d", resp.StatusCode)
+ }
+
+ // Verify that the response is valid JSON
+ var result map[string]interface{}
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ t.Errorf("invalid JSON response: %v", err)
+ }
+
+ // For error responses, verify that we have an error message
+ if resp.StatusCode != http.StatusOK {
+ if errMsg, ok := result["error"].(string); !ok || errMsg == "" {
+ t.Error("error response missing error message")
+ }
+ }
+ })
+}