blob: 4f80eef1630a98bbc57ac34ed279bb1454df6217 [file] [log] [blame]
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")
}
}
})
}