RC-1
release-tag / release-image (push) Failing after 1m18s

This commit is contained in:
2026-08-10 05:48:55 +02:00
parent 4f0ac14c49
commit 005fd6ca51
52 changed files with 9020 additions and 1 deletions
+254
View File
@@ -0,0 +1,254 @@
package server
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"image"
_ "image/jpeg"
_ "image/png"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"neuralhunt/internal/artifact"
wsx "neuralhunt/internal/ws"
"github.com/go-chi/chi/v5"
)
const maxStyleReferenceBytes = 12 << 20
func (s *Server) adminCreateCharacterAnchor(w http.ResponseWriter, r *http.Request) {
if s.artifactWorker == nil {
jsonOut(w, http.StatusServiceUnavailable, map[string]string{"error": "artifact worker unavailable"})
return
}
if err := s.artifactWorker.CreateCharacterAnchor(r.Context()); err != nil {
if errors.Is(err, artifact.ErrCharacterAnchorExists) {
jsonOut(w, http.StatusConflict, map[string]string{"error": "character anchor already exists"})
return
}
jsonOut(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
return
}
jsonOut(w, http.StatusCreated, map[string]any{
"ok": true,
"url": "/api/admin/artifact/character-anchor?ts=" + fmt.Sprint(time.Now().UnixMilli()),
})
}
func (s *Server) adminCharacterAnchorFile(w http.ResponseWriter, r *http.Request) {
path := filepath.Join(s.artifactDir, "_collection", "character_anchor.png")
b, err := os.ReadFile(path)
if err != nil || len(b) <= 1024 {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "private, no-store")
w.Header().Set("Content-Length", fmt.Sprint(len(b)))
_, _ = w.Write(b)
}
func normalizeUploadedStyle(raw []byte) (ext, contentType string, width, height int, err error) {
if len(raw) < 128 {
return "", "", 0, 0, errors.New("style reference is empty")
}
contentType = strings.Split(http.DetectContentType(raw), ";")[0]
switch contentType {
case "image/jpeg":
ext = ".jpg"
case "image/png":
ext = ".png"
default:
return "", "", 0, 0, fmt.Errorf("unsupported style image type %q; use JPEG or PNG", contentType)
}
cfg, _, err := image.DecodeConfig(bytes.NewReader(raw))
if err != nil {
return "", "", 0, 0, fmt.Errorf("decode style image: %w", err)
}
if cfg.Width < 128 || cfg.Height < 128 || cfg.Width > 8192 || cfg.Height > 8192 || int64(cfg.Width)*int64(cfg.Height) > 40_000_000 {
return "", "", 0, 0, fmt.Errorf("style image dimensions %dx%d are outside the allowed range", cfg.Width, cfg.Height)
}
return ext, contentType, cfg.Width, cfg.Height, nil
}
func writeStyleAsset(dir, name string, raw []byte) error {
if err := os.MkdirAll(dir, 0o750); err != nil {
return err
}
target := filepath.Join(dir, name)
if st, err := os.Stat(target); err == nil && st.Size() == int64(len(raw)) {
// The filename is a SHA-256 of the file contents, so an existing asset
// with the same name is the same immutable reference. This also avoids
// Windows rename-over-existing behavior.
return nil
}
tmp, err := os.CreateTemp(dir, ".style-*")
if err != nil {
return err
}
tmpName := tmp.Name()
defer os.Remove(tmpName)
if err := tmp.Chmod(0o640); err != nil {
tmp.Close()
return err
}
if _, err := tmp.Write(raw); err != nil {
tmp.Close()
return err
}
if err := tmp.Sync(); err != nil {
tmp.Close()
return err
}
if err := tmp.Close(); err != nil {
return err
}
return os.Rename(tmpName, target)
}
func (s *Server) adminTaskStyleReferencePut(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
r.Body = http.MaxBytesReader(w, r.Body, maxStyleReferenceBytes+1<<20)
if err := r.ParseMultipartForm(maxStyleReferenceBytes); err != nil {
jsonOut(w, http.StatusBadRequest, map[string]string{"error": "invalid multipart upload: " + err.Error()})
return
}
f, _, err := r.FormFile("file")
if err != nil {
jsonOut(w, http.StatusBadRequest, map[string]string{"error": "multipart field 'file' is required"})
return
}
defer f.Close()
raw, err := io.ReadAll(io.LimitReader(f, maxStyleReferenceBytes+1))
if err != nil {
jsonOut(w, http.StatusBadRequest, map[string]string{"error": "read upload: " + err.Error()})
return
}
if len(raw) > maxStyleReferenceBytes {
jsonOut(w, http.StatusRequestEntityTooLarge, map[string]string{"error": "style reference exceeds 12 MiB"})
return
}
ext, contentType, width, height, err := normalizeUploadedStyle(raw)
if err != nil {
jsonOut(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
h := sha256.Sum256(raw)
name := hex.EncodeToString(h[:]) + ext
if err := writeStyleAsset(filepath.Join(s.artifactDir, "_styles"), name, raw); err != nil {
jsonOut(w, http.StatusInternalServerError, map[string]string{"error": "store style reference: " + err.Error()})
return
}
if err := s.store.SetTaskStyleReference(r.Context(), id, name); err != nil {
jsonOut(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
_ = s.hub.Publish(r.Context(), wsx.Event{Type: "task_changed", TaskID: id, Data: map[string]string{"action": "style_reference"}})
jsonOut(w, http.StatusOK, map[string]any{
"ok": true,
"name": name,
"sha256": hex.EncodeToString(h[:]),
"content_type": contentType,
"width": width,
"height": height,
"url": "/api/admin/tasks/" + id + "/style-reference?ts=" + fmt.Sprint(time.Now().UnixMilli()),
})
}
func (s *Server) adminTaskStyleReferenceDelete(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if err := s.store.SetTaskStyleReference(r.Context(), id, ""); err != nil {
jsonOut(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
_ = s.hub.Publish(r.Context(), wsx.Event{Type: "task_changed", TaskID: id, Data: map[string]string{"action": "style_reference"}})
jsonOut(w, http.StatusOK, map[string]bool{"ok": true})
}
func serveTaskStyleReference(w http.ResponseWriter, r *http.Request, ref artifact.TaskStyleReference, cache string) {
if len(ref.Bytes) == 0 {
http.NotFound(w, r)
return
}
ct := ref.ContentType
if ct == "" {
ct = http.DetectContentType(ref.Bytes)
}
w.Header().Set("Content-Type", ct)
w.Header().Set("Cache-Control", cache)
w.Header().Set("ETag", `"`+ref.SHA256+`"`)
w.Header().Set("Content-Length", fmt.Sprint(len(ref.Bytes)))
_, _ = w.Write(ref.Bytes)
}
func (s *Server) adminTaskStyleReferenceFile(w http.ResponseWriter, r *http.Request) {
if s.artifactWorker == nil {
http.NotFound(w, r)
return
}
ref, err := s.artifactWorker.TaskStyleReference(r.Context(), chi.URLParam(r, "id"))
if err != nil {
http.NotFound(w, r)
return
}
serveTaskStyleReference(w, r, ref, "private, no-store")
}
func (s *Server) publicTaskStyleReference(w http.ResponseWriter, r *http.Request) {
if s.artifactWorker == nil {
http.NotFound(w, r)
return
}
id := chi.URLParam(r, "id")
var status string
if err := s.store.DB.QueryRowContext(r.Context(), `SELECT status FROM tasks WHERE id=?`, id).Scan(&status); err != nil || status != "active" {
http.NotFound(w, r)
return
}
ref, err := s.artifactWorker.TaskStyleReference(r.Context(), id)
if err != nil {
http.NotFound(w, r)
return
}
serveTaskStyleReference(w, r, ref, "public, max-age=300")
}
func (s *Server) adminCreatePipelineTestCard(w http.ResponseWriter, r *http.Request) {
if s.artifactWorker == nil {
jsonOut(w, http.StatusServiceUnavailable, map[string]string{"error": "artifact worker unavailable"})
return
}
id := chi.URLParam(r, "id")
path, err := s.artifactWorker.CreatePipelineTestCard(r.Context(), id)
if err != nil {
jsonOut(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
_ = path
jsonOut(w, http.StatusCreated, map[string]any{"ok": true, "url": "/api/admin/tasks/" + id + "/pipeline-test/card?ts=" + fmt.Sprint(time.Now().UnixMilli()), "api_calls": 0, "cost_usd": 0})
}
func (s *Server) adminPipelineTestCardFile(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if filepath.Base(id) != id || strings.ContainsAny(id, `/\\`) {
http.NotFound(w, r)
return
}
path := filepath.Join(s.artifactDir, "_test", id, "image.svg")
b, err := os.ReadFile(path)
if err != nil || len(b) < 128 {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "image/svg+xml")
w.Header().Set("Cache-Control", "private, no-store")
_, _ = w.Write(b)
}
+120
View File
@@ -0,0 +1,120 @@
package server
import (
"database/sql"
"net/http"
"strconv"
"time"
)
type artifactUsageRow struct {
CreatedAt time.Time `json:"created_at"`
TaskID string `json:"task_id,omitempty"`
Kind string `json:"kind"`
Model string `json:"model"`
Endpoint string `json:"endpoint"`
Size string `json:"size"`
Quality string `json:"quality"`
RequestID string `json:"request_id,omitempty"`
InputTokens int64 `json:"input_tokens"`
InputTextTokens int64 `json:"input_text_tokens"`
InputImageTokens int64 `json:"input_image_tokens"`
OutputTokens int64 `json:"output_tokens"`
TotalTokens int64 `json:"total_tokens"`
EstimatedCostUSD *float64 `json:"estimated_cost_usd,omitempty"`
PricingBasis string `json:"pricing_basis,omitempty"`
}
func (s *Server) adminArtifactUsage(w http.ResponseWriter, r *http.Request) {
now := time.Now().UTC()
dayStartMS := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).UnixMilli()
if raw := r.URL.Query().Get("day_start_ms"); raw != "" {
if v, err := strconv.ParseInt(raw, 10, 64); err == nil {
// Accept a browser-local midnight in a reasonable window. This lets the
// "today" KPI follow the admin's local calendar day without adding a
// timezone setting to the server.
candidate := time.UnixMilli(v).UTC()
if candidate.After(now.Add(-48*time.Hour)) && candidate.Before(now.Add(24*time.Hour)) {
dayStartMS = v
}
}
}
var todayCalls, todayCards, todayPriced int64
var todayCost float64
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT count(*),
COALESCE(sum(CASE WHEN kind='artifact' THEN 1 ELSE 0 END),0),
COALESCE(sum(CASE WHEN estimated_cost_usd IS NOT NULL THEN 1 ELSE 0 END),0),
COALESCE(sum(estimated_cost_usd),0)
FROM artifact_api_usage WHERE created_at>=?`, dayStartMS).Scan(&todayCalls, &todayCards, &todayPriced, &todayCost)
var cardCalls, pricedCardCalls int64
var totalCardCost, avgCardCost float64
var inputTokens, inputTextTokens, inputImageTokens, outputTokens, totalTokens int64
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT count(*),count(estimated_cost_usd),
COALESCE(sum(estimated_cost_usd),0),COALESCE(avg(estimated_cost_usd),0),
COALESCE(sum(input_tokens),0),COALESCE(sum(input_text_tokens),0),COALESCE(sum(input_image_tokens),0),
COALESCE(sum(output_tokens),0),COALESCE(sum(total_tokens),0)
FROM artifact_api_usage WHERE kind='artifact'`).Scan(
&cardCalls, &pricedCardCalls, &totalCardCost, &avgCardCost,
&inputTokens, &inputTextTokens, &inputImageTokens, &outputTokens, &totalTokens)
var anchorCalls int64
var anchorCost float64
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT count(*),COALESCE(sum(estimated_cost_usd),0)
FROM artifact_api_usage WHERE kind='character_anchor'`).Scan(&anchorCalls, &anchorCost)
recent := make([]artifactUsageRow, 0, 20)
rows, err := s.store.DB.QueryContext(r.Context(), `SELECT created_at,task_id,kind,model,endpoint,size,quality,request_id,
input_tokens,input_text_tokens,input_image_tokens,output_tokens,total_tokens,estimated_cost_usd,pricing_basis
FROM artifact_api_usage ORDER BY created_at DESC,id DESC LIMIT 20`)
if err == nil {
defer rows.Close()
for rows.Next() {
var createdMS int64
var taskID, requestID sql.NullString
var cost sql.NullFloat64
var row artifactUsageRow
if err := rows.Scan(&createdMS, &taskID, &row.Kind, &row.Model, &row.Endpoint, &row.Size, &row.Quality, &requestID,
&row.InputTokens, &row.InputTextTokens, &row.InputImageTokens, &row.OutputTokens, &row.TotalTokens, &cost, &row.PricingBasis); err != nil {
continue
}
row.CreatedAt = time.UnixMilli(createdMS).UTC()
if taskID.Valid {
row.TaskID = taskID.String
}
if requestID.Valid {
row.RequestID = requestID.String
}
if cost.Valid {
v := cost.Float64
row.EstimatedCostUSD = &v
}
recent = append(recent, row)
}
}
jsonOut(w, 200, map[string]any{
"day_start": time.UnixMilli(dayStartMS).UTC(),
"today_calls": todayCalls,
"today_cards": todayCards,
"today_priced_calls": todayPriced,
"today_cost_usd": todayCost,
"card_generations": cardCalls,
"priced_card_generations": pricedCardCalls,
"total_card_cost_usd": totalCardCost,
"avg_card_cost_usd": avgCardCost,
"cost_per_1000_usd": avgCardCost * 1000,
"anchor_generations": anchorCalls,
"anchor_cost_usd": anchorCost,
"card_usage": map[string]int64{
"input_tokens": inputTokens,
"input_text_tokens": inputTextTokens,
"input_image_tokens": inputImageTokens,
"output_tokens": outputTokens,
"total_tokens": totalTokens,
},
"cost_method": "estimated from provider-reported token usage using pinned OpenAI standard public token rates; not invoice reconciliation",
"recent": recent,
})
}
+31
View File
@@ -0,0 +1,31 @@
package server
import (
"net/http/httptest"
"strings"
"testing"
"neuralhunt/internal/auth"
)
func TestDecodeAuthAcceptsExtraJWKMembers(t *testing.T) {
body := `{"public_jwk":{"kty":"EC","crv":"P-256","x":"abc","y":"def","ext":true,"key_ops":["verify"],"alg":"ES256","use":"sig","kid":"browser-key","vendor_future_field":"ok"}}`
req := httptest.NewRequest("POST", "/api/auth/challenge", strings.NewReader(body))
var in struct {
PublicJWK auth.PublicJWK `json:"public_jwk"`
}
if err := decodeAuth(req, &in); err != nil {
t.Fatalf("decodeAuth should tolerate optional/unknown JWK metadata: %v", err)
}
if in.PublicJWK.Kty != "EC" || in.PublicJWK.Crv != "P-256" || in.PublicJWK.X != "abc" || in.PublicJWK.Y != "def" {
t.Fatalf("core JWK fields were not decoded: %+v", in.PublicJWK)
}
}
func TestDecodeAuthRejectsSecondJSONValue(t *testing.T) {
req := httptest.NewRequest("POST", "/api/auth/challenge", strings.NewReader(`{"public_jwk":{}} {"x":1}`))
var in any
if err := decodeAuth(req, &in); err == nil {
t.Fatal("expected second JSON value to be rejected")
}
}
File diff suppressed because it is too large Load Diff
+213
View File
@@ -0,0 +1,213 @@
package server
import (
"bytes"
"fmt"
"html"
"image"
"image/color"
"image/draw"
_ "image/gif"
_ "image/jpeg"
"image/png"
"net/url"
"os"
"path/filepath"
"strings"
)
// artifactLocalPath maps an artifact URI emitted by the worker back into the
// configured artifact directory. It deliberately rejects traversal and files
// outside /artifacts/ so the public preview endpoint cannot become a generic
// file reader.
func artifactLocalPath(root, artifactURI string) (string, error) {
if strings.TrimSpace(root) == "" {
return "", fmt.Errorf("artifact storage disabled")
}
u, err := url.Parse(artifactURI)
if err != nil {
return "", err
}
p := u.Path
const prefix = "/artifacts/"
i := strings.Index(p, prefix)
if i < 0 {
return "", fmt.Errorf("artifact URI outside artifact namespace")
}
rel := filepath.Clean(filepath.FromSlash(strings.TrimPrefix(p[i:], prefix)))
if rel == "." || rel == "" || filepath.IsAbs(rel) || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return "", fmt.Errorf("invalid artifact path")
}
rootAbs, err := filepath.Abs(root)
if err != nil {
return "", err
}
candidate := filepath.Join(rootAbs, rel)
candidateAbs, err := filepath.Abs(candidate)
if err != nil {
return "", err
}
check, err := filepath.Rel(rootAbs, candidateAbs)
if err != nil || check == ".." || strings.HasPrefix(check, ".."+string(filepath.Separator)) {
return "", fmt.Errorf("artifact path escapes storage")
}
return candidateAbs, nil
}
func watermarkPreviewFile(path, label string) ([]byte, string, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, "", err
}
if len(b) > 64<<20 {
return nil, "", fmt.Errorf("artifact too large for preview")
}
ext := strings.ToLower(filepath.Ext(path))
if ext == ".svg" || bytes.Contains(bytes.ToLower(b[:minInt(len(b), 512)]), []byte("<svg")) {
out, err := watermarkSVG(b, label)
return out, "image/svg+xml; charset=utf-8", err
}
img, _, err := image.Decode(bytes.NewReader(b))
if err != nil {
return nil, "", fmt.Errorf("decode preview image: %w", err)
}
bounds := img.Bounds()
if bounds.Dx() < 1 || bounds.Dy() < 1 {
return nil, "", fmt.Errorf("empty artifact image")
}
dst := image.NewNRGBA(bounds)
draw.Draw(dst, bounds, img, bounds.Min, draw.Src)
drawRasterWatermark(dst, label)
var out bytes.Buffer
if err := png.Encode(&out, dst); err != nil {
return nil, "", err
}
return out.Bytes(), "image/png", nil
}
func watermarkSVG(src []byte, label string) ([]byte, error) {
s := string(src)
i := strings.LastIndex(strings.ToLower(s), "</svg>")
if i < 0 {
return nil, fmt.Errorf("invalid svg artifact")
}
label = html.EscapeString(strings.ToUpper(strings.TrimSpace(label)))
if label == "" {
label = "NEURAL HUNT PREVIEW"
}
overlay := fmt.Sprintf(`<defs><pattern id="nh-preview-watermark" width="420" height="190" patternUnits="userSpaceOnUse" patternTransform="rotate(-24)"><text x="18" y="96" fill="white" fill-opacity="0.16" font-family="system-ui,sans-serif" font-size="28" font-weight="800" letter-spacing="5">%s</text></pattern></defs><rect x="0" y="0" width="100%%" height="100%%" fill="url(#nh-preview-watermark)" pointer-events="none"/><rect x="2%%" y="92%%" width="96%%" height="5%%" rx="12" fill="black" fill-opacity="0.30"/><text x="50%%" y="95.5%%" text-anchor="middle" fill="white" fill-opacity="0.72" font-family="system-ui,sans-serif" font-size="18" font-weight="800" letter-spacing="4">WATERMARKED LEADERBOARD PREVIEW</text>`, label)
return []byte(s[:i] + overlay + s[i:]), nil
}
var pixelFont = map[rune][7]string{
'A': {"01110", "10001", "10001", "11111", "10001", "10001", "10001"},
'E': {"11111", "10000", "10000", "11110", "10000", "10000", "11111"},
'H': {"10001", "10001", "10001", "11111", "10001", "10001", "10001"},
'I': {"11111", "00100", "00100", "00100", "00100", "00100", "11111"},
'L': {"10000", "10000", "10000", "10000", "10000", "10000", "11111"},
'N': {"10001", "11001", "10101", "10101", "10011", "10001", "10001"},
'P': {"11110", "10001", "10001", "11110", "10000", "10000", "10000"},
'R': {"11110", "10001", "10001", "11110", "10100", "10010", "10001"},
'T': {"11111", "00100", "00100", "00100", "00100", "00100", "00100"},
'U': {"10001", "10001", "10001", "10001", "10001", "10001", "01110"},
'V': {"10001", "10001", "10001", "10001", "10001", "01010", "00100"},
'W': {"10001", "10001", "10001", "10101", "10101", "10101", "01010"},
}
func drawRasterWatermark(img *image.NRGBA, label string) {
b := img.Bounds()
w, h := b.Dx(), b.Dy()
if w < 1 || h < 1 {
return
}
label = strings.ToUpper(strings.TrimSpace(label))
if label == "" {
label = "NEURAL HUNT PREVIEW"
}
// Keep the bitmap watermark large enough to survive thumbnail scaling.
scale := minInt(w, h) / 360
if scale < 1 {
scale = 1
}
if scale > 6 {
scale = 6
}
textW := pixelTextWidth(label, scale)
rowStep := 72 * scale
colStep := textW + 54*scale
for y, row := b.Min.Y+18*scale, 0; y < b.Max.Y; y, row = y+rowStep, row+1 {
offset := 0
if row%2 == 1 {
offset = -(colStep / 2)
}
for x := b.Min.X + offset; x < b.Max.X; x += colStep {
drawPixelText(img, x+scale, y+scale, label, scale, color.NRGBA{0, 0, 0, 72})
drawPixelText(img, x, y, label, scale, color.NRGBA{255, 255, 255, 48})
}
}
// Strong lower preview band so cropped screenshots still visibly carry a
// watermark. It contains a repeated NH glyph rather than metadata.
bandH := maxInt(16*scale, h/18)
bandY := b.Max.Y - bandH
draw.Draw(img, image.Rect(b.Min.X, bandY, b.Max.X, b.Max.Y), &image.Uniform{C: color.NRGBA{0, 0, 0, 92}}, image.Point{}, draw.Over)
for x := b.Min.X + 8*scale; x < b.Max.X; x += 40 * scale {
drawPixelText(img, x, bandY+4*scale, "NH", scale, color.NRGBA{255, 255, 255, 118})
}
}
func pixelTextWidth(text string, scale int) int {
if scale < 1 {
scale = 1
}
w := 0
for _, r := range text {
if r == ' ' {
w += 4 * scale
} else {
w += 6 * scale
}
}
return w
}
func drawPixelText(dst draw.Image, x, y int, text string, scale int, col color.Color) {
if scale < 1 {
scale = 1
}
cx := x
for _, r := range text {
if r == ' ' {
cx += 4 * scale
continue
}
glyph, ok := pixelFont[r]
if !ok {
cx += 6 * scale
continue
}
for gy, row := range glyph {
for gx, bit := range row {
if bit != '1' {
continue
}
rect := image.Rect(cx+gx*scale, y+gy*scale, cx+(gx+1)*scale, y+(gy+1)*scale)
draw.Draw(dst, rect, &image.Uniform{C: col}, image.Point{}, draw.Over)
}
}
cx += 6 * scale
}
}
func minInt(a, b int) int {
if a < b {
return a
}
return b
}
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
+65
View File
@@ -0,0 +1,65 @@
package server
import (
"bytes"
"image"
"image/color"
"image/png"
"os"
"path/filepath"
"strings"
"testing"
)
func TestArtifactLocalPathRejectsTraversal(t *testing.T) {
root := t.TempDir()
if _, err := artifactLocalPath(root, "/artifacts/../secret.txt"); err == nil {
t.Fatal("expected traversal rejection")
}
got, err := artifactLocalPath(root, "/artifacts/a/image.png")
if err != nil {
t.Fatal(err)
}
want := filepath.Join(root, "a", "image.png")
if got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestWatermarkSVG(t *testing.T) {
out, err := watermarkSVG([]byte(`<svg xmlns="http://www.w3.org/2000/svg"><rect width="10" height="10"/></svg>`), "NEURAL HUNT PREVIEW")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(out), "WATERMARKED LEADERBOARD PREVIEW") || !strings.Contains(string(out), "nh-preview-watermark") {
t.Fatalf("watermark missing: %s", out)
}
}
func TestWatermarkRasterProducesPNG(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "source.png")
img := image.NewNRGBA(image.Rect(0, 0, 320, 240))
for y := 0; y < 240; y++ {
for x := 0; x < 320; x++ {
img.Set(x, y, color.NRGBA{20, 40, 60, 255})
}
}
var src bytes.Buffer
if err := png.Encode(&src, img); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, src.Bytes(), 0o600); err != nil {
t.Fatal(err)
}
out, ct, err := watermarkPreviewFile(path, "NEURAL HUNT PREVIEW")
if err != nil {
t.Fatal(err)
}
if ct != "image/png" || len(out) == 0 {
t.Fatalf("unexpected preview %q %d", ct, len(out))
}
if bytes.Equal(out, src.Bytes()) {
t.Fatal("preview should differ from source")
}
}