|
@@ -2631,6 +2631,240 @@ func TestUpdateEntryStatusEndpoint(t *testing.T) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func TestGetEntryIDsEndpoint(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)
|
|
|
|
|
+
|
|
|
|
|
+ boolPtr := func(b bool) *bool { return &b }
|
|
|
|
|
+
|
|
|
|
|
+ // A new user should have no entries at all.
|
|
|
|
|
+ result, err := regularUserClient.EntryIDs(nil)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.EntryIDs == nil {
|
|
|
|
|
+ t.Fatal(`Entry IDs should not be nil`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(result.EntryIDs) != 0 {
|
|
|
|
|
+ t.Fatalf(`Expected no entry IDs for a new user, got %d`, len(result.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.Total != 0 {
|
|
|
|
|
+ t.Fatalf(`Expected total to be 0 for a new user, got %d`, result.Total)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Subscribe to a feed so there are entries.
|
|
|
|
|
+ feedID, err := regularUserClient.CreateFeed(&miniflux.FeedCreationRequest{
|
|
|
|
|
+ FeedURL: testConfig.testFeedURL,
|
|
|
|
|
+ })
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ allEntries, err := regularUserClient.FeedEntries(feedID, nil)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(allEntries.Entries) == 0 {
|
|
|
|
|
+ t.Fatal(`Expected feed to have entries`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Without filters, all entries should be returned.
|
|
|
|
|
+ result, err = regularUserClient.EntryIDs(nil)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(result.EntryIDs) != allEntries.Total {
|
|
|
|
|
+ t.Fatalf(`Expected %d entry IDs, got %d`, allEntries.Total, len(result.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.Total != allEntries.Total {
|
|
|
|
|
+ t.Fatalf(`Expected total %d, got %d`, allEntries.Total, result.Total)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Filter by status=unread: all entries should be unread initially.
|
|
|
|
|
+ unreadResult, err := regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Status: miniflux.EntryStatusUnread})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(unreadResult.EntryIDs) != allEntries.Total {
|
|
|
|
|
+ t.Fatalf(`Expected %d unread entry IDs, got %d`, allEntries.Total, len(unreadResult.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Mark one entry as read and verify status filter results update.
|
|
|
|
|
+ firstEntryID := allEntries.Entries[0].ID
|
|
|
|
|
+ if err := regularUserClient.UpdateEntries([]int64{firstEntryID}, miniflux.EntryStatusRead); err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ unreadResult, err = regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Status: miniflux.EntryStatusUnread})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(unreadResult.EntryIDs) != allEntries.Total-1 {
|
|
|
|
|
+ t.Fatalf(`Expected %d unread entry IDs after marking one as read, got %d`, allEntries.Total-1, len(unreadResult.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if unreadResult.Total != allEntries.Total-1 {
|
|
|
|
|
+ t.Fatalf(`Expected total %d after marking one as read, got %d`, allEntries.Total-1, unreadResult.Total)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for _, id := range unreadResult.EntryIDs {
|
|
|
|
|
+ if id == firstEntryID {
|
|
|
|
|
+ t.Fatalf(`Entry ID %d should not appear in unread IDs after being marked as read`, firstEntryID)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ readResult, err := regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Status: miniflux.EntryStatusRead})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(readResult.EntryIDs) != 1 || readResult.EntryIDs[0] != firstEntryID {
|
|
|
|
|
+ t.Fatalf(`Expected only entry %d in read results, got %v`, firstEntryID, readResult.EntryIDs)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Pagination: limit=1 should return 1 entry but total reflects the full unread count.
|
|
|
|
|
+ if allEntries.Total >= 2 {
|
|
|
|
|
+ pagedResult, err := regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Status: miniflux.EntryStatusUnread, Limit: 1})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(pagedResult.EntryIDs) != 1 {
|
|
|
|
|
+ t.Fatalf(`Expected 1 entry ID with limit=1, got %d`, len(pagedResult.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if pagedResult.Total != allEntries.Total-1 {
|
|
|
|
|
+ t.Fatalf(`Expected total %d with limit=1, got %d`, allEntries.Total-1, pagedResult.Total)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // offset=1 should skip the first entry.
|
|
|
|
|
+ offsetResult, err := regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Status: miniflux.EntryStatusUnread, Limit: 1, Offset: 1})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(offsetResult.EntryIDs) != 1 {
|
|
|
|
|
+ t.Fatalf(`Expected 1 entry ID with limit=1 offset=1, got %d`, len(offsetResult.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if offsetResult.EntryIDs[0] == pagedResult.EntryIDs[0] {
|
|
|
|
|
+ t.Fatalf(`Entry at offset=1 should differ from offset=0, both returned %d`, offsetResult.EntryIDs[0])
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Filter by starred=true: initially no starred entries.
|
|
|
|
|
+ starredResult, err := regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Starred: boolPtr(true)})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(starredResult.EntryIDs) != 0 {
|
|
|
|
|
+ t.Fatalf(`Expected no starred entry IDs for a new user, got %d`, len(starredResult.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Star the first entry and verify it appears in starred results.
|
|
|
|
|
+ if err := regularUserClient.ToggleStarred(firstEntryID); err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ starredResult, err = regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Starred: boolPtr(true)})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(starredResult.EntryIDs) != 1 {
|
|
|
|
|
+ t.Fatalf(`Expected 1 starred entry ID, got %d`, len(starredResult.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if starredResult.Total != 1 {
|
|
|
|
|
+ t.Fatalf(`Expected total 1, got %d`, starredResult.Total)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if starredResult.EntryIDs[0] != firstEntryID {
|
|
|
|
|
+ t.Fatalf(`Expected starred entry ID %d, got %d`, firstEntryID, starredResult.EntryIDs[0])
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // The read starred entry should appear when filtering by starred=true (read status does not affect it).
|
|
|
|
|
+ starredResult, err = regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Starred: boolPtr(true)})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(starredResult.EntryIDs) != 1 {
|
|
|
|
|
+ t.Fatalf(`Expected starred entry ID to persist after marking as read, got %d result(s)`, len(starredResult.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // starred=false should exclude the starred entry.
|
|
|
|
|
+ notStarredResult, err := regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Starred: boolPtr(false)})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for _, id := range notStarredResult.EntryIDs {
|
|
|
|
|
+ if id == firstEntryID {
|
|
|
|
|
+ t.Fatalf(`Starred entry %d should not appear in starred=false results`, firstEntryID)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Pagination with offset past the single starred result should return 0 entries but total 1.
|
|
|
|
|
+ pagedStarred, err := regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Starred: boolPtr(true), Limit: 0, Offset: 1})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(pagedStarred.EntryIDs) != 0 {
|
|
|
|
|
+ t.Fatalf(`Expected 0 entry IDs with offset=1 past the only result, got %d`, len(pagedStarred.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if pagedStarred.Total != 1 {
|
|
|
|
|
+ t.Fatalf(`Expected total 1 with offset past results, got %d`, pagedStarred.Total)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Unstarring the entry should remove it from starred=true results.
|
|
|
|
|
+ if err := regularUserClient.ToggleStarred(firstEntryID); err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ starredResult, err = regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Starred: boolPtr(true)})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(starredResult.EntryIDs) != 0 {
|
|
|
|
|
+ t.Fatalf(`Expected no starred entry IDs after unstarring, got %d`, len(starredResult.EntryIDs))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if starredResult.Total != 0 {
|
|
|
|
|
+ t.Fatalf(`Expected total 0 after unstarring, got %d`, starredResult.Total)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Invalid starred value should return 400.
|
|
|
|
|
+ _, err = regularUserClient.EntryIDs(&miniflux.EntryIDsFilter{Status: "maybe"})
|
|
|
|
|
+ if err == nil {
|
|
|
|
|
+ t.Fatal(`Expected error for invalid status parameter, got nil`)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func TestUpdateEntryEndpoint(t *testing.T) {
|
|
func TestUpdateEntryEndpoint(t *testing.T) {
|
|
|
testConfig := newIntegrationTestConfig()
|
|
testConfig := newIntegrationTestConfig()
|
|
|
if !testConfig.isConfigured() {
|
|
if !testConfig.isConfigured() {
|