210 lines
5.8 KiB
Go
210 lines
5.8 KiB
Go
|
package deadlinks
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"sort"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"code.betamike.com/mediocregopher/mediocre-go-lib/miter"
|
||
|
"github.com/stretchr/testify/assert"
|
||
|
)
|
||
|
|
||
|
type sqliteStoreHarness struct {
|
||
|
ctx context.Context
|
||
|
now time.Time
|
||
|
store *SQLiteStore
|
||
|
}
|
||
|
|
||
|
func newSQLiteStoreHarness() *sqliteStoreHarness {
|
||
|
var (
|
||
|
ctx = context.Background()
|
||
|
now = time.Now().Truncate(time.Second).UTC()
|
||
|
store = NewInMemStore()
|
||
|
)
|
||
|
|
||
|
return &sqliteStoreHarness{
|
||
|
ctx, now, store,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *sqliteStoreHarness) assertGetByStatus(
|
||
|
t *testing.T, want []Resource, status ResourceStatus,
|
||
|
) {
|
||
|
norm := func(rr []Resource) {
|
||
|
for i, r := range rr {
|
||
|
sort.Slice(r.IncomingLinkURLs, func(i, j int) bool {
|
||
|
return r.IncomingLinkURLs[i] < r.IncomingLinkURLs[j]
|
||
|
})
|
||
|
sort.Slice(r.OutgoingLinkURLs, func(i, j int) bool {
|
||
|
return r.OutgoingLinkURLs[i] < r.OutgoingLinkURLs[j]
|
||
|
})
|
||
|
rr[i] = r
|
||
|
}
|
||
|
|
||
|
sort.Slice(rr, func(i, j int) bool {
|
||
|
return rr[i].URL < rr[j].URL
|
||
|
})
|
||
|
}
|
||
|
|
||
|
got, err := miter.ToSlice(h.ctx, h.store.GetByStatus(status))
|
||
|
assert.NoError(t, err)
|
||
|
|
||
|
norm(want)
|
||
|
norm(got)
|
||
|
assert.Equal(t, want, got)
|
||
|
}
|
||
|
|
||
|
func TestSQLiteStore(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
t.Run("SetPinned", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
var (
|
||
|
h = newSQLiteStoreHarness()
|
||
|
urlA = URL("https://a.com")
|
||
|
urlB = URL("https://b.com")
|
||
|
a = Resource{URL: urlA, Pinned: true}
|
||
|
b = Resource{URL: urlB, Pinned: true}
|
||
|
)
|
||
|
|
||
|
assert.NoError(t, h.store.SetPinned(h.ctx, []URL{urlA}))
|
||
|
h.assertGetByStatus(t, nil, ResourceStatusOK)
|
||
|
h.assertGetByStatus(t, []Resource{a}, ResourceStatusUnknown)
|
||
|
|
||
|
assert.NoError(t, h.store.SetPinned(h.ctx, []URL{urlA, urlB}))
|
||
|
h.assertGetByStatus(t, nil, ResourceStatusOK)
|
||
|
h.assertGetByStatus(t, []Resource{a, b}, ResourceStatusUnknown)
|
||
|
|
||
|
a.Pinned = false
|
||
|
assert.NoError(t, h.store.SetPinned(h.ctx, []URL{urlB}))
|
||
|
h.assertGetByStatus(t, nil, ResourceStatusOK)
|
||
|
h.assertGetByStatus(t, []Resource{a, b}, ResourceStatusUnknown)
|
||
|
})
|
||
|
|
||
|
t.Run("Update", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
var (
|
||
|
h = newSQLiteStoreHarness()
|
||
|
|
||
|
urlA = URL("https://a.com")
|
||
|
urlB = URL("https://b.com")
|
||
|
urlC = URL("https://c.com")
|
||
|
|
||
|
a = Resource{URL: urlA, Pinned: true}
|
||
|
b = Resource{URL: urlB, Pinned: true}
|
||
|
c = Resource{URL: urlC}
|
||
|
)
|
||
|
|
||
|
// updating a non-existing URL should fail and make no changes
|
||
|
assert.Error(t, h.store.Update(h.ctx, h.now, urlA, ResourceStatusOK, "errstr", nil))
|
||
|
h.assertGetByStatus(t, nil, ResourceStatusUnknown)
|
||
|
h.assertGetByStatus(t, nil, ResourceStatusOK)
|
||
|
|
||
|
assert.NoError(t, h.store.SetPinned(h.ctx, []URL{urlA, urlB}))
|
||
|
|
||
|
assert.NoError(t, h.store.Update(h.ctx, h.now, urlA, ResourceStatusOK, "", nil))
|
||
|
a.LastChecked = h.now
|
||
|
a.Status = ResourceStatusOK
|
||
|
h.assertGetByStatus(t, []Resource{b}, ResourceStatusUnknown)
|
||
|
h.assertGetByStatus(t, []Resource{a}, ResourceStatusOK)
|
||
|
|
||
|
assert.NoError(t, h.store.Update(h.ctx, h.now, urlB, ResourceStatusError, "error!", nil))
|
||
|
b.LastChecked = h.now
|
||
|
b.Status = ResourceStatusError
|
||
|
b.ErrorString = "error!"
|
||
|
h.assertGetByStatus(t, nil, ResourceStatusUnknown)
|
||
|
h.assertGetByStatus(t, nil, ResourceStatusUnknown)
|
||
|
h.assertGetByStatus(t, []Resource{a}, ResourceStatusOK)
|
||
|
h.assertGetByStatus(t, []Resource{b}, ResourceStatusError)
|
||
|
|
||
|
assert.NoError(t, h.store.Update(
|
||
|
h.ctx, h.now, urlA, ResourceStatusOK, "", []URL{urlC},
|
||
|
))
|
||
|
a.OutgoingLinkURLs = []URL{urlC}
|
||
|
c.IncomingLinkURLs = []URL{urlA}
|
||
|
h.assertGetByStatus(t, []Resource{c}, ResourceStatusUnknown)
|
||
|
h.assertGetByStatus(t, []Resource{a}, ResourceStatusOK)
|
||
|
h.assertGetByStatus(t, []Resource{b}, ResourceStatusError)
|
||
|
|
||
|
assert.NoError(t, h.store.Update(
|
||
|
h.ctx, h.now, urlB, ResourceStatusOK, "", []URL{urlC},
|
||
|
))
|
||
|
b.Status = ResourceStatusOK
|
||
|
b.ErrorString = ""
|
||
|
b.OutgoingLinkURLs = []URL{urlC}
|
||
|
c.IncomingLinkURLs = []URL{urlA, urlB}
|
||
|
h.assertGetByStatus(t, []Resource{c}, ResourceStatusUnknown)
|
||
|
h.assertGetByStatus(t, []Resource{a, b}, ResourceStatusOK)
|
||
|
h.assertGetByStatus(t, nil, ResourceStatusError)
|
||
|
})
|
||
|
|
||
|
t.Run("GetURLsByLastChecked", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
var (
|
||
|
h = newSQLiteStoreHarness()
|
||
|
nowA = h.now
|
||
|
nowB = h.now.Add(1 * time.Minute)
|
||
|
|
||
|
urlA = URL("https://a.com")
|
||
|
urlB = URL("https://b.com")
|
||
|
urlC = URL("https://c.com")
|
||
|
)
|
||
|
|
||
|
assert.NoError(t, h.store.SetPinned(h.ctx, []URL{urlA, urlB, urlC}))
|
||
|
|
||
|
assert.NoError(t, h.store.Update(
|
||
|
h.ctx, nowA, urlA, ResourceStatusOK, "", nil,
|
||
|
))
|
||
|
|
||
|
assert.NoError(t, h.store.Update(
|
||
|
h.ctx, nowB, urlB, ResourceStatusOK, "", nil,
|
||
|
))
|
||
|
|
||
|
assertGetURLsByLastChecked := func(want []URL, olderThan time.Time) {
|
||
|
got, err := miter.ToSlice(h.ctx, h.store.GetURLsByLastChecked(olderThan))
|
||
|
assert.NoError(t, err)
|
||
|
assert.ElementsMatch(t, want, got)
|
||
|
}
|
||
|
|
||
|
assertGetURLsByLastChecked([]URL{urlA, urlB, urlC}, nowB.Add(1*time.Second))
|
||
|
assertGetURLsByLastChecked([]URL{urlA, urlC}, nowB)
|
||
|
assertGetURLsByLastChecked([]URL{urlA, urlC}, nowA.Add(1*time.Second))
|
||
|
assertGetURLsByLastChecked([]URL{urlC}, nowA)
|
||
|
assertGetURLsByLastChecked([]URL{urlC}, h.now)
|
||
|
})
|
||
|
|
||
|
t.Run("deleteOrphans", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
|
||
|
var (
|
||
|
h = newSQLiteStoreHarness()
|
||
|
|
||
|
urlA = URL("https://a.com")
|
||
|
urlB = URL("https://b.com")
|
||
|
urlC = URL("https://c.com")
|
||
|
urlD = URL("https://d.com")
|
||
|
|
||
|
a = Resource{URL: urlA, Pinned: true, LastChecked: h.now, OutgoingLinkURLs: []URL{urlB}}
|
||
|
b = Resource{URL: urlB, IncomingLinkURLs: []URL{urlA}}
|
||
|
)
|
||
|
|
||
|
assert.NoError(t, h.store.SetPinned(h.ctx, []URL{urlA, urlB, urlC, urlD}))
|
||
|
assert.NoError(t, h.store.SetPinned(h.ctx, []URL{urlA}))
|
||
|
|
||
|
assert.NoError(t, h.store.Update(
|
||
|
h.ctx, h.now, urlA, ResourceStatusUnknown, "", []URL{urlB},
|
||
|
))
|
||
|
|
||
|
assert.NoError(t, h.store.Update(
|
||
|
h.ctx, h.now, urlC, ResourceStatusUnknown, "", []URL{urlD},
|
||
|
))
|
||
|
|
||
|
assert.NoError(t, h.store.deleteOrphans(h.ctx))
|
||
|
h.assertGetByStatus(t, []Resource{a, b}, ResourceStatusUnknown)
|
||
|
})
|
||
|
}
|