test: add acceptance tests for summarize-new-video (RED)
Executable translation of docs/use-cases/summarize_new_video.feature: captioned video is summarized and delivered to the store sink; no-transcript video is skipped with reason "no transcript" and no delivery. Drives the engine through fake adapters (no live YouTube/brain). Intentionally RED until the engine is implemented — this is the swarm's target.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
// Package acceptance holds executable acceptance tests that mirror the Gherkin
|
||||
// scenarios in docs/use-cases/. They drive the use-case engine through FAKE
|
||||
// adapters (no live YouTube, no live brain) so the core behaviour is what's
|
||||
// under test.
|
||||
//
|
||||
// These tests are intentionally RED in the scaffold: Engine.ProcessNewVideo
|
||||
// returns usecase.ErrNotImplemented. Making them GREEN is the first build task.
|
||||
package acceptance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.d-ma.be/mathias/tapir/internal/domain"
|
||||
"gitea.d-ma.be/mathias/tapir/internal/usecase"
|
||||
)
|
||||
|
||||
// --- fake adapters ---------------------------------------------------------
|
||||
|
||||
type fakeSource struct {
|
||||
transcript domain.Transcript
|
||||
}
|
||||
|
||||
func (f fakeSource) ListSubscriptions(ctx context.Context, userID string) ([]domain.Subscription, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f fakeSource) NewVideos(ctx context.Context, sub domain.Subscription) ([]domain.Video, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f fakeSource) FetchTranscript(ctx context.Context, v domain.Video) (domain.Transcript, error) {
|
||||
return f.transcript, nil
|
||||
}
|
||||
|
||||
type fakeSummarizer struct{}
|
||||
|
||||
func (fakeSummarizer) Summarize(ctx context.Context, v domain.Video, t domain.Transcript) (domain.Summary, error) {
|
||||
return domain.Summary{
|
||||
VideoID: v.ID,
|
||||
UserID: v.UserID,
|
||||
Summary: "a summary",
|
||||
Highlights: []string{"h1"},
|
||||
Takeaways: []string{"t1"},
|
||||
AIProvider: "local",
|
||||
CreatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type recordingSink struct {
|
||||
name string
|
||||
delivered []domain.Summary
|
||||
}
|
||||
|
||||
func (s *recordingSink) Name() string { return s.name }
|
||||
func (s *recordingSink) Deliver(ctx context.Context, sum domain.Summary) error {
|
||||
s.delivered = append(s.delivered, sum)
|
||||
return nil
|
||||
}
|
||||
|
||||
func sampleVideo() domain.Video {
|
||||
return domain.Video{
|
||||
ID: "v1", UserID: "u1", SubscriptionID: "s1",
|
||||
Provider: domain.ProviderYouTube, ProviderVideoID: "yt1",
|
||||
Title: "Designing for Attention", SeenAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// --- scenarios -------------------------------------------------------------
|
||||
|
||||
// Scenario: A subscribed channel posts a video that has captions
|
||||
func TestSubscribedVideoWithCaptionsIsSummarizedAndDelivered(t *testing.T) {
|
||||
src := fakeSource{transcript: domain.Transcript{
|
||||
VideoID: "v1", UserID: "u1", Source: domain.SourceCaptions,
|
||||
Language: "en", Content: "transcript text",
|
||||
}}
|
||||
sink := &recordingSink{name: "store"}
|
||||
eng := usecase.NewEngine(src, fakeSummarizer{}, sink)
|
||||
|
||||
res, err := eng.ProcessNewVideo(context.Background(), sampleVideo())
|
||||
if err != nil {
|
||||
t.Fatalf("ProcessNewVideo returned error: %v", err)
|
||||
}
|
||||
if res.Skipped {
|
||||
t.Fatalf("expected video to be summarized, got skipped: %q", res.Reason)
|
||||
}
|
||||
if res.Summary == nil {
|
||||
t.Fatal("expected a summary, got nil")
|
||||
}
|
||||
if len(sink.delivered) != 1 {
|
||||
t.Fatalf("expected 1 delivery to store sink, got %d", len(sink.delivered))
|
||||
}
|
||||
}
|
||||
|
||||
// Scenario: A subscribed channel posts a video with no usable transcript
|
||||
func TestVideoWithNoTranscriptIsSkipped(t *testing.T) {
|
||||
src := fakeSource{transcript: domain.Transcript{
|
||||
VideoID: "v1", UserID: "u1", Source: domain.SourceNone,
|
||||
}}
|
||||
sink := &recordingSink{name: "store"}
|
||||
eng := usecase.NewEngine(src, fakeSummarizer{}, sink)
|
||||
|
||||
res, err := eng.ProcessNewVideo(context.Background(), sampleVideo())
|
||||
if err != nil {
|
||||
t.Fatalf("ProcessNewVideo returned error: %v", err)
|
||||
}
|
||||
if !res.Skipped {
|
||||
t.Fatal("expected video to be skipped (no transcript)")
|
||||
}
|
||||
if res.Reason != "no transcript" {
|
||||
t.Fatalf("expected reason %q, got %q", "no transcript", res.Reason)
|
||||
}
|
||||
if res.Summary != nil {
|
||||
t.Fatal("expected no summary for skipped video")
|
||||
}
|
||||
if len(sink.delivered) != 0 {
|
||||
t.Fatalf("expected no summary delivery, got %d", len(sink.delivered))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user