diff --git a/internal/api/api_integration_test.go b/internal/api/api_integration_test.go index c645809e..c94916f3 100644 --- a/internal/api/api_integration_test.go +++ b/internal/api/api_integration_test.go @@ -2589,6 +2589,64 @@ func TestUpdateEntryStatusEndpoint(t *testing.T) { } } +func TestUpdateEntryRemovedStatusEndpoint(t *testing.T) { + testConfig := newIntegrationTestConfig() + if !testConfig.isConfigured() { + t.Skip(skipIntegrationTestsMessage) + } + + adminClient := miniflux.NewClient(testConfig.testBaseURL, testConfig.testAdminUsername, testConfig.testAdminPassword) + + regularTestUser, err := adminClient.CreateUser(testConfig.genRandomUsername(), testConfig.testRegularPassword, false) + if err != nil { + t.Fatal(err) + } + defer adminClient.DeleteUser(regularTestUser.ID) + + regularUserClient := miniflux.NewClient(testConfig.testBaseURL, regularTestUser.Username, testConfig.testRegularPassword) + + feedID, err := regularUserClient.CreateFeed(&miniflux.FeedCreationRequest{ + FeedURL: testConfig.testFeedURL, + }) + if err != nil { + t.Fatal(err) + } + + result, err := regularUserClient.FeedEntries(feedID, nil) + if err != nil { + t.Fatalf(`Failed to get entries: %v`, err) + } + + // First we set the entry as "removed" + if err := regularUserClient.UpdateEntries([]int64{result.Entries[0].ID}, miniflux.EntryStatusRemoved); err != nil { + t.Fatal(err) + } + + entry, err := regularUserClient.Entry(result.Entries[0].ID) + if err != nil { + t.Fatal(err) + } + + if entry.Status != miniflux.EntryStatusRemoved { + t.Fatalf(`Invalid status, got %q instead of %q`, entry.Status, miniflux.EntryStatusRemoved) + } + + // Then we try to set it to "unread" + if err := regularUserClient.UpdateEntries([]int64{result.Entries[0].ID}, miniflux.EntryStatusUnread); err != nil { + t.Fatal(err) + } + + entry, err = regularUserClient.Entry(result.Entries[0].ID) + if err != nil { + t.Fatal(err) + } + + // It should stay set to "removed" + if entry.Status != miniflux.EntryStatusRemoved { + t.Fatalf(`Modified immutable status: got %q instead of %q`, entry.Status, miniflux.EntryStatusRemoved) + } +} + func TestUpdateEntryEndpoint(t *testing.T) { testConfig := newIntegrationTestConfig() if !testConfig.isConfigured() { diff --git a/internal/storage/entry.go b/internal/storage/entry.go index 784aacc9..dfad844a 100644 --- a/internal/storage/entry.go +++ b/internal/storage/entry.go @@ -378,8 +378,19 @@ func (s *Storage) ArchiveEntries(status string, days, limit int) (int64, error) // SetEntriesStatus update the status of the given list of entries. func (s *Storage) SetEntriesStatus(userID int64, entryIDs []int64, status string) error { - query := `UPDATE entries SET status=$1, changed_at=now() WHERE user_id=$2 AND id=ANY($3)` - if _, err := s.db.Exec(query, status, userID, pq.Array(entryIDs)); err != nil { + // Entries that have the model.EntryStatusRemoved status are immutable. + query := ` + UPDATE + entries + SET + status=$1, + changed_at=now() + WHERE + user_id=$2 AND + id=ANY($3) AND + status!=$4 + ` + if _, err := s.db.Exec(query, status, userID, pq.Array(entryIDs), model.EntryStatusRemoved); err != nil { return fmt.Errorf(`store: unable to update entries statuses %v: %v`, entryIDs, err) }