mirror of
https://github.com/nkanaev/yarr.git
synced 2026-06-09 18:03:19 +00:00
feedstate: implement + test
This commit is contained in:
@@ -1,31 +1,109 @@
|
||||
package storage
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FeedState struct {
|
||||
FeedID int64
|
||||
LastRefreshed time.Time
|
||||
LastError string
|
||||
|
||||
LastError *string
|
||||
HTTPLastModified string
|
||||
HTTPEtag string
|
||||
}
|
||||
|
||||
func (s *Storage) ListFeedStates() ([]FeedState, error) {
|
||||
// TODO: implement
|
||||
rows, err := s.db.Query(`
|
||||
select feed_id, last_refreshed, last_modified, etag, last_error
|
||||
from feed_states
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
states := make([]FeedState, 0)
|
||||
for rows.Next() {
|
||||
var state FeedState
|
||||
err := rows.Scan(
|
||||
&state.FeedID,
|
||||
&state.LastRefreshed,
|
||||
&state.HTTPLastModified,
|
||||
&state.HTTPEtag,
|
||||
&state.LastError,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
states = append(states, state)
|
||||
}
|
||||
return states, nil
|
||||
}
|
||||
|
||||
func (s *Storage) GetFeedState() (FeedState, error) {
|
||||
// TODO: implement
|
||||
func (s *Storage) GetFeedState(feedID int64) (*FeedState, error) {
|
||||
var state FeedState
|
||||
err := s.db.QueryRow(`
|
||||
select feed_id, last_refreshed, last_modified, etag, last_error
|
||||
from feed_states where feed_id = :id
|
||||
`, sql.Named("id", feedID)).Scan(
|
||||
&state.FeedID,
|
||||
&state.LastRefreshed,
|
||||
&state.HTTPLastModified,
|
||||
&state.HTTPEtag,
|
||||
&state.LastError,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
type UpdateFeedStateParams struct {
|
||||
LastRefreshed *time.Time
|
||||
LastError *string
|
||||
|
||||
HTTPLastModified *string
|
||||
HTTPEtag *string
|
||||
}
|
||||
|
||||
func (s *Storage) UpdateFeedState(params UpdateFeedStateParams) (bool, error) {
|
||||
// TODO: implement
|
||||
func (s *Storage) UpdateFeedState(feedID int64, params UpdateFeedStateParams) (bool, error) {
|
||||
lastError := params.LastError
|
||||
if lastError != nil && *lastError == "" {
|
||||
lastError = nil
|
||||
}
|
||||
|
||||
_, err := s.db.Exec(`
|
||||
insert into feed_states (
|
||||
feed_id
|
||||
, last_refreshed
|
||||
, last_modified
|
||||
, etag
|
||||
, last_error
|
||||
)
|
||||
values (
|
||||
:id
|
||||
, coalesce(:refreshed, 0)
|
||||
, coalesce(:last_modified, '')
|
||||
, coalesce(:etag, '')
|
||||
, coalesce(:last_error, '')
|
||||
)
|
||||
on conflict (feed_id) do update set
|
||||
last_refreshed = coalesce(:refreshed, last_refreshed),
|
||||
last_modified = coalesce(:last_modified, last_modified),
|
||||
etag = coalesce(:etag, etag),
|
||||
last_error = coalesce(:last_error, last_error)
|
||||
`,
|
||||
sql.Named("id", feedID),
|
||||
sql.Named("refreshed", params.LastRefreshed),
|
||||
sql.Named("last_modified", params.HTTPLastModified),
|
||||
sql.Named("etag", params.HTTPEtag),
|
||||
sql.Named("last_error", params.LastError),
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestUpdateFeedState_Full(t *testing.T) {
|
||||
s := testDB()
|
||||
defer s.Close()
|
||||
|
||||
f := s.CreateFeed(CreateFeedParams{Title: "Test", FeedLink: "http://example.com"})
|
||||
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
errMsg := "error"
|
||||
lmod := "today"
|
||||
etag := "v1"
|
||||
|
||||
ok, err := s.UpdateFeedState(f.Id, UpdateFeedStateParams{
|
||||
LastRefreshed: &now,
|
||||
LastError: &errMsg,
|
||||
HTTPLastModified: &lmod,
|
||||
HTTPEtag: &etag,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Error("expected true")
|
||||
}
|
||||
|
||||
state, err := s.GetFeedState(f.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if state == nil {
|
||||
t.Fatal("expected state, got nil")
|
||||
}
|
||||
if !state.LastRefreshed.Equal(now) {
|
||||
t.Errorf("expected %v, got %v", now, state.LastRefreshed)
|
||||
}
|
||||
if state.LastError == nil || *state.LastError != errMsg {
|
||||
t.Errorf("expected %s, got %v", errMsg, state.LastError)
|
||||
}
|
||||
if state.HTTPLastModified != lmod {
|
||||
t.Errorf("expected %s, got %s", lmod, state.HTTPLastModified)
|
||||
}
|
||||
if state.HTTPEtag != etag {
|
||||
t.Errorf("expected %s, got %s", etag, state.HTTPEtag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateFeedState_Partial(t *testing.T) {
|
||||
s := testDB()
|
||||
defer s.Close()
|
||||
|
||||
f := s.CreateFeed(CreateFeedParams{Title: "Test", FeedLink: "http://example.com"})
|
||||
etag := "v1"
|
||||
s.UpdateFeedState(f.Id, UpdateFeedStateParams{HTTPEtag: &etag})
|
||||
|
||||
newErr := "new error"
|
||||
_, err := s.UpdateFeedState(f.Id, UpdateFeedStateParams{
|
||||
LastError: &newErr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
state, err := s.GetFeedState(f.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if state.LastError == nil || *state.LastError != newErr {
|
||||
t.Errorf("expected %s, got %v", newErr, state.LastError)
|
||||
}
|
||||
if state.HTTPEtag != etag {
|
||||
t.Errorf("etag should be unchanged, got %s", state.HTTPEtag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateFeedState_ClearError(t *testing.T) {
|
||||
s := testDB()
|
||||
defer s.Close()
|
||||
|
||||
f := s.CreateFeed(CreateFeedParams{Title: "Test", FeedLink: "http://example.com"})
|
||||
errMsg := "error"
|
||||
s.UpdateFeedState(f.Id, UpdateFeedStateParams{LastError: &errMsg})
|
||||
|
||||
empty := ""
|
||||
_, err := s.UpdateFeedState(f.Id, UpdateFeedStateParams{
|
||||
LastError: &empty,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
state, err := s.GetFeedState(f.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if state.LastError == nil || *state.LastError != "" {
|
||||
t.Errorf("expected empty string error, got %v", state.LastError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListFeedStates(t *testing.T) {
|
||||
s := testDB()
|
||||
defer s.Close()
|
||||
|
||||
f1 := s.CreateFeed(CreateFeedParams{Title: "F1", FeedLink: "L1"})
|
||||
f2 := s.CreateFeed(CreateFeedParams{Title: "F2", FeedLink: "L2"})
|
||||
|
||||
errMsg := "fail"
|
||||
etag := "e"
|
||||
s.UpdateFeedState(f1.Id, UpdateFeedStateParams{LastError: &errMsg})
|
||||
s.UpdateFeedState(f2.Id, UpdateFeedStateParams{HTTPEtag: &etag})
|
||||
|
||||
states, err := s.ListFeedStates()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(states) != 2 {
|
||||
t.Errorf("expected 2 states, got %d", len(states))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ var migrations = []func(*sql.Tx) error{
|
||||
m10_add_item_medialinks,
|
||||
m11_add_item_last_arrived,
|
||||
m12_remove_feed_sizes,
|
||||
m13_consolidate_feed_states,
|
||||
}
|
||||
|
||||
var maxVersion = int64(len(migrations))
|
||||
@@ -345,3 +346,32 @@ func m12_remove_feed_sizes(tx *sql.Tx) error {
|
||||
_, err := tx.Exec(`drop table if exists feed_sizes`)
|
||||
return err
|
||||
}
|
||||
|
||||
func m13_consolidate_feed_states(tx *sql.Tx) error {
|
||||
sql := `
|
||||
create table feed_states (
|
||||
feed_id references feeds(id) on delete cascade unique,
|
||||
last_refreshed datetime not null default 0,
|
||||
last_modified string not null default '',
|
||||
etag string not null default '',
|
||||
last_error string not null default ''
|
||||
);
|
||||
|
||||
insert into feed_states (feed_id, last_refreshed, last_modified, etag, last_error)
|
||||
select
|
||||
f.id,
|
||||
coalesce(h.last_refreshed, 0),
|
||||
coalesce(h.last_modified, ''),
|
||||
coalesce(h.etag, ''),
|
||||
coalesce(e.error, '')
|
||||
from feeds f
|
||||
left join http_states h on f.id = h.feed_id
|
||||
left join feed_errors e on f.id = e.feed_id
|
||||
where h.feed_id is not null or e.feed_id is not null;
|
||||
|
||||
drop table http_states;
|
||||
drop table feed_errors;
|
||||
`
|
||||
_, err := tx.Exec(sql)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -38,3 +38,7 @@ func New(path string) (*Storage, error) {
|
||||
}
|
||||
return &Storage{db: db}, nil
|
||||
}
|
||||
|
||||
func (s *Storage) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user