clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/standalone-relay
tinyrelay / internal / daemon / custom_views_validation_test.go
custom_views_validation_test.go 4.8K
1package daemon23import (4 "context"5 "encoding/base64"6 "encoding/binary"7 "hash/crc32"8 "net/http"9 "strings"10 "testing"11 "time"12)1314func TestCustomViewTransformCountsSuccessfulBlocks(t *testing.T) {15 for _, tc := range []struct {16 name string17 artifacts []map[string]any18 status string19 stored int20 failures int21 retries int22 }{23 {"complete", []map[string]any{svgArtifact(0), svgArtifact(1)}, "ok", 2, 0, 0},24 {"partial", []map[string]any{svgArtifact(0)}, "partial", 1, 0, 0},25 {"duplicate", []map[string]any{svgArtifact(0), svgArtifact(0)}, "partial", 1, 0, 0},26 {"invalid", []map[string]any{{"block": 0, "type": "image/png", "body": "invalid"}}, "no valid artifacts", 0, 1, 1},27 {"empty", nil, "no valid artifacts", 0, 1, 1},28 } {29 t.Run(tc.name, func(t *testing.T) {30 tenant, server, _ := viewTenant(t, []int{1}, nil)31 server.answer(http.StatusOK, tc.artifacts...)32 note := signedEvent(t, testOwnerSecret, 1, time.Now().Unix(), nil, "```mermaid\nA\n```\n\n```mermaid\nB\n```")33 if err := publishAs(t, tenant, note); err != nil {34 t.Fatal(err)35 }36 runPendingView(t, tenant, "diagrams", note.ID)37 var status string38 var failures, stored int39 if err := tenant.store.DB().QueryRowContext(context.Background(), `SELECT last_status,failures FROM custom_views WHERE name='diagrams'`).Scan(&status, &failures); err != nil {40 t.Fatal(err)41 }42 if err := tenant.store.DB().QueryRowContext(context.Background(), `SELECT count(*) FROM custom_view_artifacts`).Scan(&stored); err != nil {43 t.Fatal(err)44 }45 retries := len(pendingViewIntents(t, tenant, "diagrams", note.ID))46 if status != tc.status || stored != tc.stored || failures != tc.failures || retries != tc.retries {47 t.Fatalf("status=%q stored=%d failures=%d retries=%d; want %q %d %d %d", status, stored, failures, retries, tc.status, tc.stored, tc.failures, tc.retries)48 }49 })50 }51}5253func TestCustomViewInvalidArtifactsRetryThenPause(t *testing.T) {54 tenant, server, _ := viewTenant(t, []int{1}, nil)55 server.answer(http.StatusOK, map[string]any{"block": 0, "type": "image/svg+xml", "body": "<svg><script/></svg>"})56 note := signedEvent(t, testOwnerSecret, 1, time.Now().Unix(), nil, "```mermaid\nA\n```")57 if err := publishAs(t, tenant, note); err != nil {58 t.Fatal(err)59 }60 for range 3 {61 runPendingView(t, tenant, "diagrams", note.ID)62 }63 if got := pendingViewIntents(t, tenant, "diagrams", note.ID); len(got) != 0 {64 t.Fatalf("invalid artifacts exceeded retry limit: %+v", got)65 }66 ctx := context.Background()67 if _, err := tenant.store.DB().ExecContext(ctx, `UPDATE custom_views SET failures=19 WHERE name='diagrams'`); err != nil {68 t.Fatal(err)69 }70 other := signedEvent(t, testOwnerSecret, 1, time.Now().Unix(), nil, "```mermaid\nB\n```")71 if err := publishAs(t, tenant, other); err != nil {72 t.Fatal(err)73 }74 runPendingView(t, tenant, "diagrams", other.ID)75 var failures, enabled int76 var status string77 if err := tenant.store.DB().QueryRowContext(ctx, `SELECT failures,enabled,last_status FROM custom_views WHERE name='diagrams'`).Scan(&failures, &enabled, &status); err != nil {78 t.Fatal(err)79 }80 if failures != 20 || enabled != 0 || status != "paused after 20 failures: no valid artifacts" {81 t.Fatalf("failures=%d enabled=%d status=%q", failures, enabled, status)82 }83}8485func TestCustomViewSVGReferenceBoundaries(t *testing.T) {86 for _, tc := range []struct {87 name, body string88 valid bool89 }{90 {"XML declaration", `<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg"/>`, true},91 {"literal text", `<svg><text>@import url(example)</text></svg>`, true},92 {"CSS comment", `<svg><style>/* colors */ .x{fill:url(#paint)}</style></svg>`, true},93 {"style chunks", `<svg><style>.x{fill:u<![CDATA[rl]]>(https://example.com/x)}</style></svg>`, false},94 {"nested style", `<svg><style>.x{fill:url(https://example.com/x)}<style/></style></svg>`, false},95 {"XML base", `<svg xml:base="https://example.com/"><use href="#secret"/></svg>`, false},96 {"attribute import", `<svg style="@import 'https://example.com/a.css'"/>`, false},97 {"other namespace", `<svg xmlns="http://www.w3.org/1999/xhtml"/>`, false},98 {"stylesheet instruction", `<?xml-stylesheet href="https://example.com/a.css"?><svg/>`, false},99 } {100 t.Run(tc.name, func(t *testing.T) {101 if err := checkSVG(tc.body); (err == nil) != tc.valid {102 t.Fatalf("valid=%v, error=%v", tc.valid, err)103 }104 })105 }106}107108func TestCustomViewRejectsOversizedPNGDimensions(t *testing.T) {109 image := []byte(testPNGRaw)110 binary.BigEndian.PutUint32(image[16:20], 8192)111 binary.BigEndian.PutUint32(image[20:24], 8192)112 binary.BigEndian.PutUint32(image[29:33], crc32.ChecksumIEEE(image[12:29]))113 _, err := checkArtifact(viewArtifact{Type: "image/png", Body: base64.StdEncoding.EncodeToString(image)}, 1024)114 if err == nil || !strings.Contains(err.Error(), "dimensions") {115 t.Fatalf("oversized PNG error=%v", err)116 }117}