160 lines
5.6 KiB
Go
160 lines
5.6 KiB
Go
package bulk
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func testLogger() *slog.Logger {
|
|
return slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
}
|
|
|
|
func TestRenderBatchUsesFixedCoreAndReplacesSubject(t *testing.T) {
|
|
var mu sync.Mutex
|
|
var subjects []string
|
|
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/v1/render" {
|
|
t.Fatalf("unexpected core path %q", r.URL.Path)
|
|
}
|
|
if got := r.URL.Query().Get("extent"); got != "partial" {
|
|
t.Fatalf("template extent = %q", got)
|
|
}
|
|
subject := r.URL.Query().Get("subject")
|
|
mu.Lock()
|
|
subjects = append(subjects, subject)
|
|
mu.Unlock()
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"subject": subject,
|
|
"declarationUrl": "https://public.example/declaration",
|
|
"badgeUrl": "https://public.example/badge.svg",
|
|
"manifestUrl": "https://public.example/manifest.json",
|
|
"html": "<a>ok</a>",
|
|
"markdown": "[ok]",
|
|
"jsonLd": map[string]any{"@type": "AIUsageDeclaration", "subject": subject},
|
|
})
|
|
}))
|
|
defer core.Close()
|
|
|
|
h, err := New(Config{
|
|
ListenAddress: ":0", CoreInternalURL: core.URL,
|
|
DisclosureBaseURL: "https://public.example", GeneratorURL: "https://public.example",
|
|
PublicName: "Bulk", MaxURLs: 10, Workers: 2, RequestTimeout: 2 * time.Second,
|
|
}, testLogger())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
body := `{"template":"extent=partial&subject=https%3A%2F%2Fold.example%2Fignored","subjects":["https://content.example/a","https://content.example/b"]}`
|
|
r := httptest.NewRequest(http.MethodPost, "/api/render-batch", strings.NewReader(body))
|
|
r.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status %d: %s", w.Code, w.Body.String())
|
|
}
|
|
var response batchResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(response.Results) != 2 || !response.Results[0].OK || !response.Results[1].OK {
|
|
t.Fatalf("unexpected results: %#v", response.Results)
|
|
}
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if len(subjects) != 2 {
|
|
t.Fatalf("core calls = %d", len(subjects))
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, subject := range subjects {
|
|
seen[subject] = true
|
|
}
|
|
if !seen["https://content.example/a"] || !seen["https://content.example/b"] || seen["https://old.example/ignored"] {
|
|
t.Fatalf("subjects sent to core: %#v", subjects)
|
|
}
|
|
}
|
|
|
|
func TestRenderBatchRejectsNonHTTPSubjectWithoutCallingCore(t *testing.T) {
|
|
calls := 0
|
|
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls++
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer core.Close()
|
|
|
|
h, err := New(Config{ListenAddress: ":0", CoreInternalURL: core.URL, DisclosureBaseURL: "https://public.example", GeneratorURL: "https://public.example", PublicName: "Bulk", MaxURLs: 10, Workers: 1, RequestTimeout: 2 * time.Second}, testLogger())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := httptest.NewRequest(http.MethodPost, "/api/render-batch", strings.NewReader(`{"template":"extent=partial","subjects":["file:///etc/passwd"]}`))
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status %d: %s", w.Code, w.Body.String())
|
|
}
|
|
var response batchResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(response.Results) != 1 || response.Results[0].OK || !strings.Contains(response.Results[0].Error, "http(s)") {
|
|
t.Fatalf("unexpected response: %#v", response.Results)
|
|
}
|
|
if calls != 0 {
|
|
t.Fatalf("core was called %d times for invalid subject", calls)
|
|
}
|
|
}
|
|
|
|
func TestRenderBatchHonoursConfiguredLimit(t *testing.T) {
|
|
h, err := New(Config{ListenAddress: ":0", CoreInternalURL: "http://127.0.0.1:9", DisclosureBaseURL: "https://public.example", GeneratorURL: "https://public.example", PublicName: "Bulk", MaxURLs: 1, Workers: 1, RequestTimeout: time.Second}, testLogger())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := httptest.NewRequest(http.MethodPost, "/api/render-batch", strings.NewReader(`{"template":"extent=partial","subjects":["https://example.org/a","https://example.org/b"]}`))
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
if w.Code != http.StatusRequestEntityTooLarge {
|
|
t.Fatalf("status %d: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestBulkUIUsesAccessibleDisclosurePreview(t *testing.T) {
|
|
h, err := New(Config{
|
|
ListenAddress: ":0", CoreInternalURL: "http://127.0.0.1:9",
|
|
DisclosureBaseURL: "https://public.example", GeneratorURL: "https://public.example",
|
|
PublicName: "Bulk", MaxURLs: 10, Workers: 1, RequestTimeout: time.Second,
|
|
}, testLogger())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
indexReq := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
indexRes := httptest.NewRecorder()
|
|
h.ServeHTTP(indexRes, indexReq)
|
|
if indexRes.Code != http.StatusOK {
|
|
t.Fatalf("index status %d: %s", indexRes.Code, indexRes.Body.String())
|
|
}
|
|
if !strings.Contains(indexRes.Body.String(), "<th>Kennzeichnung</th>") {
|
|
t.Fatalf("bulk result table does not expose the disclosure preview column")
|
|
}
|
|
|
|
jsReq := httptest.NewRequest(http.MethodGet, "/static/bulk.js", nil)
|
|
jsRes := httptest.NewRecorder()
|
|
h.ServeHTTP(jsRes, jsReq)
|
|
if jsRes.Code != http.StatusOK {
|
|
t.Fatalf("bulk.js status %d: %s", jsRes.Code, jsRes.Body.String())
|
|
}
|
|
body := jsRes.Body.String()
|
|
for _, required := range []string{"buildDisclosurePreview", "accessibleText", "SVG (nur Grafik)"} {
|
|
if !strings.Contains(body, required) {
|
|
t.Fatalf("bulk.js missing accessible preview marker %q", required)
|
|
}
|
|
}
|
|
}
|