func TestDataCreateUserHandlesNameUniqueness(t *testing.T) { pool := newConnPool(t) u := newUser() _, err := data.CreateUser(pool, u) if err != nil { t.Fatal(err) } u = newUser() _, err = data.CreateUser(pool, u) if err != (data.DuplicationError{Field: "name"}) { t.Fatalf("Expected %v, got %v", data.DuplicationError{Field: "name"}, err) } }
func TestDataCreateUserHandlesEmailUniqueness(t *testing.T) { pool := newConnPool(t) u := newUser() u.Email = data.NewString("*****@*****.**") _, err := data.CreateUser(pool, u) if err != nil { t.Fatal(err) } u.Name = data.NewString("othername") _, err = data.CreateUser(pool, u) if err != (data.DuplicationError{Field: "email"}) { t.Fatalf("Expected %v, got %v", data.DuplicationError{Field: "email"}, err) } }
func TestDataSubscriptions(t *testing.T) { pool := newConnPool(t) userID, err := data.CreateUser(pool, newUser()) if err != nil { t.Fatal(err) } url := "http://foo" err = data.InsertSubscription(pool, userID, url) if err != nil { t.Fatal(err) } subscriptions, err := data.SelectSubscriptions(pool, userID) if err != nil { t.Fatal(err) } if len(subscriptions) != 1 { t.Fatalf("Found %d subscriptions, expected 1", len(subscriptions)) } if subscriptions[0].URL.Value != url { t.Fatalf("Expected %v, got %v", url, subscriptions[0].URL) } }
func TestDataCopySubscriptionsForUserAsJSON(t *testing.T) { pool := newConnPool(t) userID, err := data.CreateUser(pool, newUser()) if err != nil { t.Fatal(err) } buffer := &bytes.Buffer{} err = data.CopySubscriptionsForUserAsJSON(pool, buffer, userID) if err != nil { t.Fatalf("Failed when no subscriptions: %v", err) } err = data.InsertSubscription(pool, userID, "http://foo") if err != nil { t.Fatal(err) } buffer.Reset() err = data.CopySubscriptionsForUserAsJSON(pool, buffer, userID) if err != nil { t.Fatal(err) } if bytes.Contains(buffer.Bytes(), []byte("foo")) != true { t.Errorf("Expected %v, got %v", true, bytes.Contains(buffer.Bytes(), []byte("foo"))) } }
func TestDataDeleteSubscription(t *testing.T) { pool := newConnPool(t) userID, err := data.CreateUser(pool, newUser()) if err != nil { t.Fatal(err) } err = data.InsertSubscription(pool, userID, "http://foo") if err != nil { t.Fatal(err) } subscriptions, err := data.SelectSubscriptions(pool, userID) if err != nil { t.Fatal(err) } if len(subscriptions) != 1 { t.Fatalf("Found %d subscriptions, expected 1", len(subscriptions)) } feedID := subscriptions[0].FeedID.Value update := &data.ParsedFeed{Name: "baz", Items: []data.ParsedItem{ {URL: "http://baz/bar", Title: "Baz", PublicationTime: data.NewTime(time.Now())}, }} nullString := data.String{Status: data.Null} err = data.UpdateFeedWithFetchSuccess(pool, feedID, update, nullString, time.Now().Add(-20*time.Minute)) if err != nil { t.Fatal(err) } err = data.DeleteSubscription(pool, userID, feedID) if err != nil { t.Fatal(err) } subscriptions, err = data.SelectSubscriptions(pool, userID) if err != nil { t.Fatal(err) } if len(subscriptions) != 0 { t.Errorf("Found %d subscriptions, expected 0", len(subscriptions)) } // feed should have been deleted as it was the last user staleFeeds, err := data.GetFeedsUncheckedSince(pool, time.Now()) if err != nil { t.Fatal(err) } if len(staleFeeds) != 0 { t.Errorf("Found %d staleFeeds, expected 0", len(staleFeeds)) } }
func TestGetAccountHandler(t *testing.T) { pool := newConnPool(t) user := &data.User{ Name: data.NewString("test"), Email: data.NewString("*****@*****.**"), } SetPassword(user, "password") userID, err := data.CreateUser(pool, user) if err != nil { t.Fatal(err) } user, err = data.SelectUserByPK(pool, userID) if err != nil { t.Fatal(err) } req, err := http.NewRequest("GET", "http://example.com/", nil) if err != nil { t.Fatal(err) } env := &environment{user: user, pool: pool} w := httptest.NewRecorder() GetAccountHandler(w, req, env) if w.Code != 200 { t.Fatalf("Expected HTTP status 200, instead received %d", w.Code) } var resp struct { ID int32 `json:"id"` Name string `json:"name"` Email string `json:"email"` } decoder := json.NewDecoder(w.Body) if err := decoder.Decode(&resp); err != nil { t.Fatal(err) } if user.ID.Value != resp.ID { t.Errorf("Expected id %d, instead received %d", user.ID.Value, resp.ID) } if user.Name.Value != resp.Name { t.Errorf("Expected name %s, instead received %s", user.Name.Value, resp.Name) } if user.Email.Value != resp.Email { t.Errorf("Expected email %s, instead received %s", user.Email.Value, resp.Email) } }
func TestResetPasswordHandlerTokenMatchestUsedPasswordReset(t *testing.T) { pool := newConnPool(t) user := &data.User{ Name: data.NewString("test"), Email: data.NewString("*****@*****.**"), } SetPassword(user, "password") userID, err := data.CreateUser(pool, user) if err != nil { t.Fatalf("repo.CreateUser returned error: %v", err) } _, localhost, _ := net.ParseCIDR("127.0.0.1/32") pwr := &data.PasswordReset{ Token: data.NewString("0123456789abcdef"), Email: data.NewString("*****@*****.**"), UserID: data.NewInt32(userID), RequestTime: data.NewTime(time.Now()), RequestIP: data.NewIPNet(*localhost), CompletionTime: data.NewTime(time.Now()), CompletionIP: data.NewIPNet(*localhost), } err = data.InsertPasswordReset(pool, pwr) if err != nil { t.Fatalf("repo.CreatePasswordReset returned error: %v", err) } buf := bytes.NewBufferString(`{"token": "0123456789abcdef", "password": "******"}`) req, err := http.NewRequest("POST", "http://example.com/", buf) if err != nil { t.Fatalf("http.NewRequest returned error: %v", err) } env := &environment{pool: pool} w := httptest.NewRecorder() ResetPasswordHandler(w, req, env) if w.Code != 404 { t.Errorf("Expected HTTP status %d, instead received %d", 404, w.Code) } user, err = data.SelectUserByPK(pool, userID) if err != nil { t.Fatalf("repo.GetUser returned error: %v", err) } if IsPassword(user, "bigsecret") { t.Error("Expected password not to be changed but it was") } }
func BenchmarkDataGetUser(b *testing.B) { pool := newConnPool(b) userID, err := data.CreateUser(pool, newUser()) if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := data.SelectUserByPK(pool, userID) if err != nil { b.Fatal(err) } } }
func BenchmarkDataGetUserByName(b *testing.B) { pool := newConnPool(b) user := newUser() _, err := data.CreateUser(pool, user) if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := data.SelectUserByName(pool, user.Name.Value) if err != nil { b.Fatal(err) } } }
func TestDataSessions(t *testing.T) { pool := newConnPool(t) userID, err := data.CreateUser(pool, newUser()) if err != nil { t.Fatal(err) } sessionID := []byte("deadbeef") err = data.InsertSession(pool, &data.Session{ ID: data.NewBytes(sessionID), UserID: data.NewInt32(userID), }, ) if err != nil { t.Fatal(err) } user, err := data.SelectUserBySessionID(pool, sessionID) if err != nil { t.Fatal(err) } if user.ID.Value != userID { t.Errorf("Expected %v, got %v", userID, user.ID) } err = data.DeleteSession(pool, sessionID) if err != nil { t.Fatal(err) } _, err = data.SelectUserBySessionID(pool, sessionID) if err != data.ErrNotFound { t.Fatalf("Expected %v, got %v", data.ErrNotFound, err) } err = data.DeleteSession(pool, sessionID) if err != data.ErrNotFound { t.Fatalf("Expected %v, got %v", notFound, err) } }
func TestExportOPML(t *testing.T) { pool := newConnPool(t) userID, err := data.CreateUser(pool, &data.User{ Name: data.NewString("test"), Email: data.NewString("*****@*****.**"), PasswordDigest: data.NewBytes([]byte("digest")), PasswordSalt: data.NewBytes([]byte("salt")), }) if err != nil { t.Fatal(err) } err = data.InsertSubscription(pool, userID, "http://example.com/feed.rss") if err != nil { t.Fatal(err) } req, err := http.NewRequest("GET", "http://example.com/", nil) if err != nil { t.Fatal(err) } env := &environment{pool: pool} env.user = &data.User{ID: data.NewInt32(userID), Name: data.NewString("test")} w := httptest.NewRecorder() ExportFeedsHandler(w, req, env) if w.Code != 200 { t.Fatalf("Expected HTTP status 200, instead received %d", w.Code) } expected := `<?xml version="1.0" encoding="UTF-8"?> <opml version="1.0"><head><title>The Pithy Reader Export for test</title></head><body><outline text="http://example.com/feed.rss" title="http://example.com/feed.rss" type="rss" xmlUrl="http://example.com/feed.rss"></outline></body></opml>` if w.Body.String() != expected { t.Fatalf("Expected:\n%s\nGot:\n%s\n", expected, w.Body.String()) } }
func RegisterHandler(w http.ResponseWriter, req *http.Request, env *environment) { var registration struct { Name string `json:"name"` Email string `json:"email"` Password string `json:"password"` } decoder := json.NewDecoder(req.Body) if err := decoder.Decode(®istration); err != nil { w.WriteHeader(422) fmt.Fprintf(w, "Error decoding request: %v", err) return } if registration.Name == "" { w.WriteHeader(422) fmt.Fprintln(w, `Request must include the attribute "name"`) return } if len(registration.Name) > 30 { w.WriteHeader(422) fmt.Fprintln(w, `"name" must be less than 30 characters`) return } err := validatePassword(registration.Password) if err != nil { w.WriteHeader(422) fmt.Fprintln(w, err) return } user := &data.User{} user.Name = data.NewString(registration.Name) user.Email = newStringFallback(registration.Email, data.Undefined) SetPassword(user, registration.Password) userID, err := data.CreateUser(env.pool, user) if err != nil { if err, ok := err.(data.DuplicationError); ok { w.WriteHeader(422) fmt.Fprintf(w, `"%s" is already taken`, err.Field) return } else { http.Error(w, "Internal server error", http.StatusInternalServerError) return } } sessionID, err := genSessionID() if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } err = data.InsertSession(env.pool, &data.Session{ ID: data.NewBytes(sessionID), UserID: data.NewInt32(userID), }, ) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) var response struct { Name string `json:"name"` SessionID string `json:"sessionID"` } response.Name = registration.Name response.SessionID = hex.EncodeToString(sessionID) encoder := json.NewEncoder(w) encoder.Encode(response) }
func TestUpdateAccountHandler(t *testing.T) { origEmail := "*****@*****.**" origPassword := "******" var tests = []struct { descr string reqEmail string reqExistingPassword string reqNewPassword string respCode int actualEmail string actualPassword string }{ { descr: "Update email and password", reqEmail: "*****@*****.**", reqExistingPassword: origPassword, reqNewPassword: "******", respCode: 200, actualEmail: "*****@*****.**", actualPassword: "******", }, { descr: "Update email", reqEmail: "*****@*****.**", reqExistingPassword: origPassword, reqNewPassword: "", respCode: 200, actualEmail: "*****@*****.**", actualPassword: origPassword, }, { descr: "Deny update of email and password", reqEmail: "*****@*****.**", reqExistingPassword: "******", reqNewPassword: "******", respCode: 422, actualEmail: origEmail, actualPassword: origPassword, }, { descr: "Deny update of email", reqEmail: "*****@*****.**", reqExistingPassword: "******", reqNewPassword: "", respCode: 422, actualEmail: origEmail, actualPassword: origPassword, }, } for _, tt := range tests { pool := newConnPool(t) user := &data.User{ Name: data.NewString("test"), Email: data.NewString(origEmail), } SetPassword(user, origPassword) userID, err := data.CreateUser(pool, user) if err != nil { t.Errorf("%s: repo.CreateUser returned error: %v", tt.descr, err) continue } user, err = data.SelectUserByPK(pool, userID) if err != nil { t.Errorf("%s: repo.GetUser returned error: %v", tt.descr, err) continue } buf := bytes.NewBufferString(`{ "email": "` + tt.reqEmail + `", "existingPassword": "******", "newPassword": "******" }`) req, err := http.NewRequest("PATCH", "http://example.com/", buf) if err != nil { t.Errorf("%s: http.NewRequest returned error: %v", tt.descr, err) continue } env := &environment{user: user, pool: pool} w := httptest.NewRecorder() UpdateAccountHandler(w, req, env) if w.Code != tt.respCode { t.Errorf("%s: Expected HTTP status %d, instead received %d", tt.descr, tt.respCode, w.Code) continue } user, err = data.SelectUserByPK(pool, userID) if err != nil { t.Errorf("%s: repo.GetUser returned error: %v", tt.descr, err) continue } if user.Email.Value != tt.actualEmail { t.Errorf("%s: Expected email %s, instead received %s", tt.descr, tt.actualEmail, user.Email.Value) } if !IsPassword(user, tt.actualPassword) { t.Errorf("%s: Expected password to be %s, but it wasn't", tt.descr, tt.actualPassword) } } }
func TestRequestPasswordResetHandler(t *testing.T) { var tests = []struct { descr string mailer *testMailer userEmail string reqEmail string remoteAddr string remoteHost string sentMailTo string }{ { descr: "Email does not match user", mailer: &testMailer{}, userEmail: "*****@*****.**", reqEmail: "*****@*****.**", remoteAddr: "192.168.0.1:54678", remoteHost: "192.168.0.1/32", }, { descr: "Email matches user", mailer: &testMailer{}, userEmail: "*****@*****.**", reqEmail: "*****@*****.**", remoteAddr: "192.168.0.1:54678", remoteHost: "192.168.0.1/32", sentMailTo: "*****@*****.**", }, } for _, tt := range tests { pool := newConnPool(t) user := &data.User{ Name: data.NewString("test"), Email: data.NewString(tt.userEmail), } SetPassword(user, "password") userID, err := data.CreateUser(pool, user) if err != nil { t.Errorf("%s: repo.CreateUser returned error: %v", tt.descr, err) continue } buf := bytes.NewBufferString(`{"email": "` + tt.reqEmail + `"}`) req, err := http.NewRequest("POST", "http://example.com/", buf) if err != nil { t.Errorf("%s: http.NewRequest returned error: %v", tt.descr, err) continue } req.RemoteAddr = tt.remoteAddr env := &environment{user: user, pool: pool, logger: getLogger(t), mailer: tt.mailer} w := httptest.NewRecorder() RequestPasswordResetHandler(w, req, env) if w.Code != 200 { t.Errorf("%s: Expected HTTP status %d, instead received %d", tt.descr, 200, w.Code) continue } // Need to reach down pgx because repo interface doesn't need any get // interface besides by token, but for this test we need to know the token var token string err = pool.QueryRow("select token from password_resets").Scan(&token) if err != nil { t.Errorf("%s: pool.QueryRow Scan returned error: %v", tt.descr, err) continue } pwr, err := data.SelectPasswordResetByPK(env.pool, token) if err != nil { t.Errorf("%s: repo.GetPasswordReset returned error: %v", tt.descr, err) continue } if pwr.Email.Value != tt.reqEmail { t.Errorf("%s: PasswordReset.Email should be %s, but instead is %v", tt.descr, tt.reqEmail, pwr.Email) } if pwr.RequestIP.Value.String() != tt.remoteHost { t.Errorf("%s: PasswordReset.RequestIP should be %s, but instead is %v", tt.descr, tt.remoteHost, pwr.RequestIP) } if tt.reqEmail == tt.userEmail && userID != pwr.UserID.Value { t.Errorf("%s: PasswordReset.UserID should be %d, but instead is %v", tt.descr, userID, pwr.UserID) } if tt.reqEmail != tt.userEmail && pwr.UserID.Status != data.Null { t.Errorf("%s: PasswordReset.UserID should be nil, but instead is %v", tt.descr, pwr.UserID) } sentMails := tt.mailer.sentPasswordResetMails if tt.sentMailTo == "" { if len(sentMails) != 0 { t.Errorf("%s: Expected to not send any reset mails, instead sent %d", tt.descr, len(sentMails)) } continue } if len(sentMails) != 1 { t.Errorf("%s: Expected to send 1 reset mail, instead sent %d", tt.descr, len(sentMails)) continue } if sentMails[0].to != tt.sentMailTo { t.Errorf("%s: Expected to send reset mail to %s, instead sent it to %d", tt.descr, tt.sentMailTo, sentMails[0].to) } if sentMails[0].token != pwr.Token.Value { t.Errorf("%s: Reset mail (%v) and password reset (%v) do not have the same token", tt.descr, sentMails[0].token, pwr.Token) } } }
func TestResetPasswordHandlerTokenMatchestValidPasswordReset(t *testing.T) { pool := newConnPool(t) user := &data.User{ Name: data.NewString("test"), Email: data.NewString("*****@*****.**"), } SetPassword(user, "password") userID, err := data.CreateUser(pool, user) if err != nil { t.Fatalf("repo.CreateUser returned error: %v", err) } _, requestIP, _ := net.ParseCIDR("127.0.0.1/32") pwr := &data.PasswordReset{ Token: data.NewString("0123456789abcdef"), Email: data.NewString("*****@*****.**"), UserID: data.NewInt32(userID), RequestTime: data.NewTime(time.Now()), RequestIP: data.NewIPNet(*requestIP), } err = data.InsertPasswordReset(pool, pwr) if err != nil { t.Fatalf("repo.CreatePasswordReset returned error: %v", err) } buf := bytes.NewBufferString(`{"token": "0123456789abcdef", "password": "******"}`) req, err := http.NewRequest("POST", "http://example.com/", buf) if err != nil { t.Fatalf("http.NewRequest returned error: %v", err) } env := &environment{pool: pool} w := httptest.NewRecorder() ResetPasswordHandler(w, req, env) if w.Code != 200 { t.Errorf("Expected HTTP status %d, instead received %d", 200, w.Code) } user, err = data.SelectUserByPK(pool, userID) if err != nil { t.Fatalf("repo.GetUser returned error: %v", err) } if !IsPassword(user, "bigsecret") { t.Error("Expected password to be changed but it was not") } var response struct { Name string `json:"name"` SessionID string `json:"sessionID"` } decoder := json.NewDecoder(w.Body) if err := decoder.Decode(&response); err != nil { t.Errorf("Unable to decode response: %v", err) } }
// This function is a nasty copy and paste of testRepositoryUpdateFeedWithFetchSuccess // Fix me when refactoring tests func TestDataUpdateFeedWithFetchSuccessWithoutPublicationTime(t *testing.T) { pool := newConnPool(t) userID, err := data.CreateUser(pool, newUser()) if err != nil { t.Fatal(err) } now := time.Now() url := "http://bar" err = data.InsertSubscription(pool, userID, url) if err != nil { t.Fatal(err) } subscriptions, err := data.SelectSubscriptions(pool, userID) if err != nil { t.Fatal(err) } if len(subscriptions) != 1 { t.Fatalf("Found %d subscriptions, expected 1", len(subscriptions)) } feedID := subscriptions[0].FeedID.Value update := &data.ParsedFeed{Name: "baz", Items: []data.ParsedItem{ {URL: "http://baz/bar", Title: "Baz"}, }} nullString := data.String{Status: data.Null} err = data.UpdateFeedWithFetchSuccess(pool, feedID, update, nullString, now) if err != nil { t.Fatal(err) } buffer := &bytes.Buffer{} err = data.CopyUnreadItemsAsJSONByUserID(pool, buffer, userID) if err != nil { t.Fatal(err) } type UnreadItemsFromJSON struct { ID int32 `json:id` } var unreadItems []UnreadItemsFromJSON err = json.Unmarshal(buffer.Bytes(), &unreadItems) if err != nil { t.Fatal(err) } if len(unreadItems) != 1 { t.Fatalf("Found %d unreadItems, expected 1", len(unreadItems)) } // Update again and ensure item does not get created again err = data.UpdateFeedWithFetchSuccess(pool, feedID, update, nullString, now) if err != nil { t.Fatal(err) } buffer.Reset() err = data.CopyUnreadItemsAsJSONByUserID(pool, buffer, userID) if err != nil { t.Fatal(err) } err = json.Unmarshal(buffer.Bytes(), &unreadItems) if err != nil { t.Fatal(err) } if len(unreadItems) != 1 { t.Fatalf("Found %d unreadItems, expected 1", len(unreadItems)) } }
func TestDataUsersLifeCycle(t *testing.T) { pool := newConnPool(t) input := &data.User{ Name: data.NewString("test"), Email: data.NewString("*****@*****.**"), PasswordDigest: data.NewBytes([]byte("digest")), PasswordSalt: data.NewBytes([]byte("salt")), } userID, err := data.CreateUser(pool, input) if err != nil { t.Fatal(err) } user, err := data.SelectUserByName(pool, input.Name.Value) if err != nil { t.Fatal(err) } if user.ID.Value != userID { t.Errorf("Expected %v, got %v", userID, user.ID) } if user.Name != input.Name { t.Errorf("Expected %v, got %v", input.Name, user.Name) } if user.Email != input.Email { t.Errorf("Expected %v, got %v", input.Email, user.Email) } if bytes.Compare(user.PasswordDigest.Value, input.PasswordDigest.Value) != 0 { t.Errorf("Expected user (%v) and input (%v) PasswordDigest to match, but they did not", user.PasswordDigest, input.PasswordDigest) } if bytes.Compare(user.PasswordSalt.Value, input.PasswordSalt.Value) != 0 { t.Errorf("Expected user (%v), and input (%v) PasswordSalt to match, but they did not", user.PasswordSalt, input.PasswordSalt) } user, err = data.SelectUserByEmail(pool, input.Email.Value) if err != nil { t.Fatal(err) } if user.ID.Value != userID { t.Errorf("Expected %v, got %v", userID, user.ID) } if user.Name != input.Name { t.Errorf("Expected %v, got %v", input.Name, user.Name) } if user.Email != input.Email { t.Errorf("Expected %v, got %v", input.Email, user.Email) } if bytes.Compare(user.PasswordDigest.Value, input.PasswordDigest.Value) != 0 { t.Errorf("Expected user (%v) and input (%v) PasswordDigest to match, but they did not", user.PasswordDigest, input.PasswordDigest) } if bytes.Compare(user.PasswordSalt.Value, input.PasswordSalt.Value) != 0 { t.Errorf("Expected user (%v), and input (%v) PasswordSalt to match, but they did not", user.PasswordSalt, input.PasswordSalt) } user, err = data.SelectUserByPK(pool, userID) if err != nil { t.Fatal(err) } if user.ID.Value != userID { t.Errorf("Expected %v, got %v", userID, user.ID) } if user.Name != input.Name { t.Errorf("Expected %v, got %v", input.Name, user.Name) } if user.Email != input.Email { t.Errorf("Expected %v, got %v", input.Email, user.Email) } if bytes.Compare(user.PasswordDigest.Value, input.PasswordDigest.Value) != 0 { t.Errorf("Expected user (%v) and input (%v) PasswordDigest to match, but they did not", user.PasswordDigest, input.PasswordDigest) } if bytes.Compare(user.PasswordSalt.Value, input.PasswordSalt.Value) != 0 { t.Errorf("Expected user (%v), and input (%v) PasswordSalt to match, but they did not", user.PasswordSalt, input.PasswordSalt) } }
func TestDataFeeds(t *testing.T) { pool := newConnPool(t) userID, err := data.CreateUser(pool, newUser()) if err != nil { t.Fatal(err) } now := time.Now() fiveMinutesAgo := now.Add(-5 * time.Minute) tenMinutesAgo := now.Add(-10 * time.Minute) fifteenMinutesAgo := now.Add(-15 * time.Minute) update := &data.ParsedFeed{Name: "baz", Items: make([]data.ParsedItem, 0)} // Create a feed url := "http://bar" err = data.InsertSubscription(pool, userID, url) if err != nil { t.Fatal(err) } // A new feed has never been fetched -- it should need fetching staleFeeds, err := data.GetFeedsUncheckedSince(pool, tenMinutesAgo) if err != nil { t.Fatal(err) } if len(staleFeeds) != 1 { t.Fatalf("Found %d stale feed, expected 1", len(staleFeeds)) } if staleFeeds[0].URL.Value != url { t.Errorf("Expected %v, got %v", url, staleFeeds[0].URL) } feedID := staleFeeds[0].ID.Value nullString := data.String{Status: data.Null} // Update feed as of now err = data.UpdateFeedWithFetchSuccess(pool, feedID, update, nullString, now) if err != nil { t.Fatal(err) } // feed should no longer be stale staleFeeds, err = data.GetFeedsUncheckedSince(pool, tenMinutesAgo) if err != nil { t.Fatal(err) } if len(staleFeeds) != 0 { t.Fatalf("Found %d stale feed, expected 0", len(staleFeeds)) } // Update feed to be old enough to need refresh err = data.UpdateFeedWithFetchSuccess(pool, feedID, update, nullString, fifteenMinutesAgo) if err != nil { t.Fatal(err) } // It should now need fetching staleFeeds, err = data.GetFeedsUncheckedSince(pool, tenMinutesAgo) if err != nil { t.Fatal(err) } if len(staleFeeds) != 1 { t.Fatalf("Found %d stale feed, expected 1", len(staleFeeds)) } if staleFeeds[0].ID.Value != feedID { t.Errorf("Expected %v, got %v", feedID, staleFeeds[0].ID) } // But update feed with a recent failed fetch err = data.UpdateFeedWithFetchFailure(pool, feedID, "something went wrong", fiveMinutesAgo) if err != nil { t.Fatal(err) } // feed should no longer be stale staleFeeds, err = data.GetFeedsUncheckedSince(pool, tenMinutesAgo) if err != nil { t.Fatal(err) } if len(staleFeeds) != 0 { t.Fatalf("Found %d stale feed, expected 0", len(staleFeeds)) } }