The first pilot user sat in Manual mode reading "new summaries land gradually, check back tomorrow" — copy that only makes sense in Automatic mode. Manual mode never auto-summarizes, so the banner promised delivery that would never come. The list page now reads the user's summarize mode and shows mode-correct copy: - Auto: unchanged "land gradually" backlog note. - Manual: "new videos appear here but are not summarized automatically — use the Summarize button" plus a "Switch to Automatic" link to /account. The connected-but-empty first-run state is likewise mode-aware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
98 lines
3.1 KiB
Go
98 lines
3.1 KiB
Go
package web
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseYouTubeVideoID(t *testing.T) {
|
|
const id = "dQw4w9WgXcQ"
|
|
ok := []struct {
|
|
name, in string
|
|
}{
|
|
{"watch", "https://www.youtube.com/watch?v=" + id},
|
|
{"watch no www", "https://youtube.com/watch?v=" + id},
|
|
{"watch m", "https://m.youtube.com/watch?v=" + id},
|
|
{"watch extra params", "https://www.youtube.com/watch?v=" + id + "&t=42s&list=PLxyz"},
|
|
{"watch param after", "https://www.youtube.com/watch?list=PLxyz&v=" + id},
|
|
{"short link", "https://youtu.be/" + id},
|
|
{"short link param", "https://youtu.be/" + id + "?si=abcd&t=1"},
|
|
{"shorts", "https://www.youtube.com/shorts/" + id},
|
|
{"embed", "https://www.youtube.com/embed/" + id},
|
|
{"bare id", id},
|
|
{"http scheme", "http://youtube.com/watch?v=" + id},
|
|
{"no scheme", "youtube.com/watch?v=" + id},
|
|
{"trailing space", " https://youtu.be/" + id + " "},
|
|
}
|
|
for _, c := range ok {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
got, err := parseYouTubeVideoID(c.in)
|
|
if err != nil {
|
|
t.Fatalf("parseYouTubeVideoID(%q) error: %v", c.in, err)
|
|
}
|
|
if got != id {
|
|
t.Fatalf("parseYouTubeVideoID(%q) = %q, want %q", c.in, got, id)
|
|
}
|
|
})
|
|
}
|
|
|
|
bad := []struct {
|
|
name, in string
|
|
}{
|
|
{"empty", ""},
|
|
{"blank", " "},
|
|
{"vimeo", "https://vimeo.com/123456789"},
|
|
{"other host", "https://example.com/watch?v=" + id},
|
|
{"watch no id", "https://www.youtube.com/watch?v="},
|
|
{"short id", "https://youtu.be/abc"},
|
|
{"long id", "https://youtu.be/" + id + "extra"},
|
|
{"bad chars", "https://youtu.be/dQw4w9Wg!cQ"},
|
|
{"not a url", "just some text"},
|
|
{"channel url", "https://www.youtube.com/@somechannel"},
|
|
}
|
|
for _, c := range bad {
|
|
t.Run("reject "+c.name, func(t *testing.T) {
|
|
if got, err := parseYouTubeVideoID(c.in); err == nil {
|
|
t.Fatalf("parseYouTubeVideoID(%q) = %q, want error", c.in, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListPageShowsPasteFormOnlyWhenConnected(t *testing.T) {
|
|
render := func(connected bool) string {
|
|
var buf bytes.Buffer
|
|
if err := ListPage(listBuckets{}, Filter{}, PipelineStats{}, "", connected, nil, true).Render(context.Background(), &buf); err != nil {
|
|
t.Fatalf("render: %v", err)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
html := render(true)
|
|
if !strings.Contains(html, `name="url"`) || !strings.Contains(html, `action="/paste"`) {
|
|
t.Errorf("connected feed must show the paste form")
|
|
}
|
|
if strings.Contains(render(false), `name="url"`) {
|
|
t.Errorf("disconnected feed must not show the paste form")
|
|
}
|
|
}
|
|
|
|
func TestFilterMatchesMultipleChannels(t *testing.T) {
|
|
f := Filter{Channels: []string{"Acme", "Zeta"}}
|
|
row := func(ch string) store.SummaryRow { return store.SummaryRow{ChannelTitle: ch, Summarized: true} }
|
|
|
|
rows := []store.SummaryRow{row("Acme"), row("Beta"), row("Zeta")}
|
|
got := f.apply(rows)
|
|
if len(got) != 2 || got[0].ChannelTitle != "Acme" || got[1].ChannelTitle != "Zeta" {
|
|
t.Fatalf("multi-channel filter = %+v, want Acme+Zeta only", got)
|
|
}
|
|
|
|
// Empty selection = no channel constraint (all pass).
|
|
if n := len(Filter{}.apply(rows)); n != 3 {
|
|
t.Fatalf("no channel filter should pass all rows, got %d", n)
|
|
}
|
|
}
|