package logic import ( "context" "errors" "strings" "testing" "time" "spark-server/internal/infrastructure/id" friendlogic "spark-server/internal/module/friend/logic" friendmodel "spark-server/internal/module/friend/model" squarelogic "spark-server/internal/module/square/logic" squaremodel "spark-server/internal/module/square/model" userentity "spark-server/internal/module/user/entity" "spark-server/internal/module/user/logictest" "spark-server/internal/pkg/errs" ) func TestParseRSSAndFormat(t *testing.T) { body := []byte(` Demo News Hello & World https://example.com/a <p>Short summary here.</p> `) items, err := parseFeed(body, "https://example.com/rss") if err != nil || len(items) != 1 { t.Fatalf("%+v %v", items, err) } if items[0].Title != "Hello & World" || items[0].Link != "https://example.com/a" { t.Fatalf("%+v", items[0]) } text := FormatPostText(items[0]) if !strings.Contains(text, "Demo News") || !strings.Contains(text, "https://example.com/a") { t.Fatalf("text=%q", text) } } func TestPublishBotMediaTTLAndDedupe(t *testing.T) { ids, err := id.New(2) if err != nil { t.Fatal(err) } users := logictest.Users(ids, nil) friends := friendlogic.NewService(friendmodel.NewMemoryRepo(), users, ids, nil) square := squarelogic.NewService(squaremodel.NewMemoryRepo(), friends, users, nil, ids) ctx := t.Context() u, err := users.AuthenticateEmail(ctx, "test01@spark.com", "1", &userentity.LoginDevice{ DeviceID: 9, DeviceType: userentity.DeviceIOS, }) if err != nil { t.Fatal(err) } in := &squarelogic.BotPublishInput{ Text: "title\n\nsum\n\nhttps://example.com/x", SourceURL: "https://example.com/x", TTL: time.Hour, } v, err := square.PublishBotMedia(ctx, u.ID, in) if err != nil || v == nil { t.Fatalf("%v %v", v, err) } _, err = square.PublishBotMedia(ctx, u.ID, in) if !errors.Is(err, errs.ErrDuplicate) { t.Fatalf("want duplicate got %v", err) } feed, err := square.Feed(ctx, u.ID, 0, 20) if err != nil || len(feed.Items) < 1 { t.Fatalf("feed %+v %v", feed, err) } n, err := square.ExpireBotPosts(ctx, time.Now().Add(2*time.Hour), 10) if err != nil || n < 1 { t.Fatalf("expire %d %v", n, err) } feed2, err := square.Feed(ctx, u.ID, 0, 20) if err != nil { t.Fatal(err) } for _, it := range feed2.Items { if it.ID == v.ID { t.Fatal("expired post still visible") } } } func TestServiceDisabledWithoutDeps(t *testing.T) { svc := NewService(nil, nil, nil, nil, Options{Enabled: true}) if svc.Enabled() { t.Fatal("enabled without deps") } } type stubUsers struct { u *userentity.User } func (s stubUsers) GetByEmail(context.Context, string) (*userentity.User, error) { if s.u == nil { return nil, errs.ErrNotFound } return s.u, nil } type stubSquare struct { published int expired int } func (s *stubSquare) PublishBotMedia(context.Context, int64, *squarelogic.BotPublishInput) (*squarelogic.View, error) { s.published++ return &squarelogic.View{ID: 1}, nil } func (s *stubSquare) ExpireBotPosts(context.Context, time.Time, int) (int, error) { s.expired++ return 0, nil } func TestTickExpiresEvenIfFeedFails(t *testing.T) { sq := &stubSquare{} users := stubUsers{u: &userentity.User{ID: 7, Nickname: "Bot"}} svc := NewService(nil, sq, users, nil, Options{ Enabled: true, Interval: time.Minute, TTL: time.Hour, BotIndexes: []int{1}, }) svc.opts.Feeds = []string{"http://127.0.0.1:1/nope"} err := svc.Tick(t.Context()) if err == nil { t.Fatal("expected feed error") } if sq.expired != 1 { t.Fatalf("expire called %d", sq.expired) } }