Files
FlowReader/internal/utils/text_test.go
T
Antigravity AgentandClaude Opus 5.5 03e57e4308 fix(backend): harden security and speed up feeds and article API
Security:
- WebSocket events are routed to their owner only (no cross-user leak);
  hub close is idempotent (fixes double-close panic), adds ping/pong and
  write deadlines.
- Session tokens stored as SHA-256 (migration 008 keeps sessions valid);
  single-query auth middleware puts the user in the request context.
- Client IP only trusts X-Forwarded-For from TRUSTED_PROXIES; rate limiter
  map is bounded; per-user limit on AI summaries.
- Argon2id at OWASP minimum with a concurrency cap; constant-time login
  for unknown emails; atomic first-admin bootstrap; REGISTRATION_ENABLED.
- CSP/HSTS/COOP headers, same-origin guard on mutations, body size limits,
  wider SSRF denylist, bounded feed/page/AI response reads, generic errors.
- Upgrade chi, pgx, x/net, x/text, x/crypto (known CVEs); commit go.sum.

Performance:
- List endpoints return a plain-text excerpt and reading time instead of
  full HTML; content is sanitized once at ingest (legacy rows backfilled).
- Keyset pagination on (sort_at, id) with matching partial indexes;
  redundant indexes dropped (migration 007).
- Fetcher: bounded worker pool, conditional GET (ETag/Last-Modified),
  exponential backoff, dedupe before insert, column-safe truncation,
  retention-aware ingest, per-user refresh coalescing.
- Read/favorite/read-all are single ownership-scoped statements.
- gzip compression, immutable caching for hashed assets, path-safe SPA
  handler, server timeouts; expired sessions purged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:34:08 +02:00

59 lines
1.5 KiB
Go

package utils
import (
"net"
"strings"
"testing"
"unicode/utf8"
)
func TestPlainText(t *testing.T) {
got := PlainText(`<p>Bonjour&nbsp;<b>le</b> monde</p><script>alert(1)</script><p>Fin &amp; suite</p>`)
want := "Bonjour le monde Fin & suite"
if got != want {
t.Fatalf("PlainText = %q, want %q", got, want)
}
}
func TestExcerptCutsOnWordBoundary(t *testing.T) {
s := strings.Repeat("mot ", 100)
got := Excerpt(s, 50)
if !strings.HasSuffix(got, "…") || utf8.RuneCountInString(got) > 51 {
t.Fatalf("unexpected excerpt %q", got)
}
if strings.Contains(got, "mo…") {
t.Fatalf("excerpt split a word: %q", got)
}
}
func TestTruncateRunesKeepsUTF8Valid(t *testing.T) {
got := TruncateRunes("éééé", 2)
if got != "éé" || !utf8.ValidString(got) {
t.Fatalf("TruncateRunes = %q", got)
}
}
func TestReadingMinutes(t *testing.T) {
cases := map[int]int{0: 1, 1: 1, 238: 1, 239: 2, 2380: 10}
for words, want := range cases {
if got := ReadingMinutes(words); got != want {
t.Errorf("ReadingMinutes(%d) = %d, want %d", words, got, want)
}
}
}
func TestIsDisallowedIP(t *testing.T) {
blocked := []string{"127.0.0.1", "10.1.2.3", "192.168.1.1", "169.254.169.254", "::1",
"64:ff9b::a00:1", "2002:a00:1::1", "198.18.0.1", "100.64.0.1", "::ffff:127.0.0.1"}
for _, s := range blocked {
if !isDisallowedIP(net.ParseIP(s)) {
t.Errorf("%s should be blocked", s)
}
}
for _, s := range []string{"1.1.1.1", "2606:4700:4700::1111"} {
if isDisallowedIP(net.ParseIP(s)) {
t.Errorf("%s should be allowed", s)
}
}
}