1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
package test

import (
	"fmt"
	"rss-kakapo/dal"
	"rss-kakapo/logic"
	"rss-kakapo/shared"
	"slices"
	"testing"
	"time"
)

type tootExtract struct {
	postTime time.Time
	id       int64
}

const purgeHandle = "some.site.com.feed"

func statusId(e tootExtract) string {
	return fmt.Sprintf("https://kakapo.example.com/u/%s/status/%d", purgeHandle, e.id)
}

// setupFeedFollowerTest returns a feed follower on a fresh database, together
// with that database. Its check loop runs, but finds nothing due: the only
// account is the birb, which is never checked.
func setupFeedFollowerTest(t *testing.T) (*shared.Config, dal.IRepo, logic.IFeedFollower) {
	cfg := &shared.Config{
		Host: "kakapo.example.com",
		Birb: &shared.UserInfo{User: "birb", Published: time.Now()},
	}
	repo := newTestRepo(t, cfg)
	ff := logic.NewFeedFollower(cfg, newTestLogger(), nopUserAgent{}, repo,
		nil, nil, &fakeTexts{}, nil)
	return cfg, repo, ff
}

func test_Feed_Follower_Purge_Old_Posts(t *testing.T,
	postExtracts []tootExtract, fromBefore *time.Time, minCount int) {

	cfg, repo, ff := setupFeedFollowerTest(t)

	const handle = purgeHandle
	if _, err := repo.AddAccountIfNotExist(&dal.Account{
		CreatedAt: time.Now(), Handle: handle, UserUrl: "https://kakapo.example.com/u/" + handle,
	}, ""); err != nil {
		t.Fatal(err)
	}
	acct, err := repo.GetAccount(handle)
	if err != nil {
		t.Fatal(err)
	}
	for _, e := range postExtracts {
		execSql(t, cfg, `INSERT INTO toots (account_id, tooted_at, status_id, content)
			VALUES (?, ?, ?, '')`,
			acct.Id, e.postTime, statusId(e))
	}

	// Purge items beyond minCount that are older than 2 days
	if err := ff.PurgeOldPosts(acct, minCount, 2); err != nil {
		t.Fatal(err)
	}

	// Exactly the toots tooted after fromBefore must be left (all of them, if
	// nothing was to be purged).
	var want []string
	for _, e := range postExtracts {
		if fromBefore == nil || e.postTime.After(*fromBefore) {
			want = append(want, statusId(e))
		}
	}
	left, err := repo.GetTootExtracts(acct.Id)
	if err != nil {
		t.Fatal(err)
	}
	var got []string
	for _, toot := range left {
		got = append(got, toot.StatusId)
	}
	slices.Sort(want)
	slices.Sort(got)
	if !slices.Equal(got, want) {
		t.Errorf("left toots: %v, want %v", got, want)
	}
}

func Test_Feed_Follower_Purge_Old_Posts_Scenarios(t *testing.T) {
	now := time.Now().UTC()
	tootExtracts := []tootExtract{
		{now.Add(-3 * time.Hour), int64(getNextId())},
		{now.Add(-52 * time.Hour), int64(getNextId())},
		{now.Add(-1 * time.Hour), int64(getNextId())},
		{now.Add(-49 * time.Hour), int64(getNextId())},
		{now.Add(-2 * time.Hour), int64(getNextId())},
	}
	test_Feed_Follower_Purge_Old_Posts(t, tootExtracts, nil, 5)
	test_Feed_Follower_Purge_Old_Posts(t, tootExtracts, &tootExtracts[1].postTime, 4)
	test_Feed_Follower_Purge_Old_Posts(t, tootExtracts, &tootExtracts[3].postTime, 3)
	test_Feed_Follower_Purge_Old_Posts(t, tootExtracts, &tootExtracts[3].postTime, 2)

	// A toot just after the cutoff must survive. Without it every other toot
	// is hours away from the cutoff, so a purge that deleted slightly too much
	// would go unnoticed.
	justAfter := append(tootExtracts[:len(tootExtracts):len(tootExtracts)],
		tootExtract{tootExtracts[1].postTime.Add(time.Minute), int64(getNextId())})
	test_Feed_Follower_Purge_Old_Posts(t, justAfter, &tootExtracts[1].postTime, 5)
}