Sfoglia il codice sorgente

Add integration tests for entries

Frédéric Guillot 8 anni fa
parent
commit
8781648af9

+ 2 - 2
Gopkg.lock

@@ -35,13 +35,13 @@
   branch = "master"
   name = "github.com/lib/pq"
   packages = [".","hstore","oid"]
-  revision = "8c6ee72f3e6bcb1542298dd5f76cb74af9742cec"
+  revision = "83612a56d3dd153a94a629cd64925371c9adad78"
 
 [[projects]]
   branch = "master"
   name = "github.com/miniflux/miniflux-go"
   packages = ["."]
-  revision = "2efd82e81054cf01433e81c419a7c84e62e6a52c"
+  revision = "c5788cd2d2248ee9fc148f3852dda7e24fe54cfa"
 
 [[projects]]
   name = "github.com/tdewolff/minify"

+ 2 - 1
README.md

@@ -29,11 +29,12 @@ TODO
 - [X] Bookmarklet
 - [ ] External integrations (Pinboard, Wallabag...)
 - [ ] Gzip compression
-- [ ] Integration tests
+- [X] Integration tests
 - [X] Flush history
 - [X] OAuth2
 - [ ] Bookmarks
 - [ ] Touch events
+- [ ] Fever API?
 
 Credits
 -------

+ 206 - 0
integration_test.go

@@ -771,6 +771,212 @@ func TestGetFeeds(t *testing.T) {
 	}
 }
 
+func TestGetAllFeedEntries(t *testing.T) {
+	username := getRandomUsername()
+	client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)
+	_, err := client.CreateUser(username, testStandardPassword, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client = miniflux.NewClient(testBaseURL, username, testStandardPassword)
+	categories, err := client.Categories()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	feedID, err := client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	allResults, err := client.FeedEntries(feedID, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if allResults.Total == 0 {
+		t.Fatal(`Invalid number of entries`)
+	}
+
+	if allResults.Entries[0].Title == "" {
+		t.Fatal(`Invalid entry title`)
+	}
+
+	filteredResults, err := client.FeedEntries(feedID, &miniflux.Filter{Limit: 1, Offset: 5})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if allResults.Total != filteredResults.Total {
+		t.Fatal(`Total should always contains the total number of items regardless of filters`)
+	}
+
+	if allResults.Entries[0].ID == filteredResults.Entries[0].ID {
+		t.Fatal(`Filtered entries should be different than previous result`)
+	}
+}
+
+func TestGetAllEntries(t *testing.T) {
+	username := getRandomUsername()
+	client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)
+	_, err := client.CreateUser(username, testStandardPassword, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client = miniflux.NewClient(testBaseURL, username, testStandardPassword)
+	categories, err := client.Categories()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	resultWithoutSorting, err := client.Entries(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if resultWithoutSorting.Total == 0 {
+		t.Fatal(`Invalid number of entries`)
+	}
+
+	resultWithStatusFilter, err := client.Entries(&miniflux.Filter{Status: miniflux.EntryStatusRead})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if resultWithStatusFilter.Total != 0 {
+		t.Fatal(`We should have 0 read entries`)
+	}
+
+	resultWithDifferentSorting, err := client.Entries(&miniflux.Filter{Order: "published_at", Direction: "asc"})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if resultWithDifferentSorting.Entries[0].Title == resultWithoutSorting.Entries[0].Title {
+		t.Fatalf(`The items should be sorted differently "%v" vs "%v"`, resultWithDifferentSorting.Entries[0].Title, resultWithoutSorting.Entries[0].Title)
+	}
+}
+
+func TestInvalidFilters(t *testing.T) {
+	username := getRandomUsername()
+	client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)
+	_, err := client.CreateUser(username, testStandardPassword, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client = miniflux.NewClient(testBaseURL, username, testStandardPassword)
+	categories, err := client.Categories()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = client.Entries(&miniflux.Filter{Status: "invalid"})
+	if err == nil {
+		t.Fatal(`Using invalid status should raise an error`)
+	}
+
+	_, err = client.Entries(&miniflux.Filter{Direction: "invalid"})
+	if err == nil {
+		t.Fatal(`Using invalid direction should raise an error`)
+	}
+
+	_, err = client.Entries(&miniflux.Filter{Order: "invalid"})
+	if err == nil {
+		t.Fatal(`Using invalid order should raise an error`)
+	}
+}
+
+func TestGetEntry(t *testing.T) {
+	username := getRandomUsername()
+	client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)
+	_, err := client.CreateUser(username, testStandardPassword, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client = miniflux.NewClient(testBaseURL, username, testStandardPassword)
+	categories, err := client.Categories()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	result, err := client.Entries(&miniflux.Filter{Limit: 1})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	entry, err := client.Entry(result.Entries[0].FeedID, result.Entries[0].ID)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if entry.ID != result.Entries[0].ID {
+		t.Fatal("Wrong entry returned")
+	}
+}
+
+func TestUpdateStatus(t *testing.T) {
+	username := getRandomUsername()
+	client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)
+	_, err := client.CreateUser(username, testStandardPassword, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client = miniflux.NewClient(testBaseURL, username, testStandardPassword)
+	categories, err := client.Categories()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = client.CreateFeed("https://miniflux.net/feed", categories[0].ID)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	result, err := client.Entries(&miniflux.Filter{Limit: 1})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = client.UpdateEntries([]int64{result.Entries[0].ID}, miniflux.EntryStatusRead)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	entry, err := client.Entry(result.Entries[0].FeedID, result.Entries[0].ID)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if entry.Status != miniflux.EntryStatusRead {
+		t.Fatal("The entry status should be updated")
+	}
+
+	err = client.UpdateEntries([]int64{result.Entries[0].ID}, "invalid")
+	if err == nil {
+		t.Fatal(`Invalid entry status should ne be accepted`)
+	}
+}
+
 func getRandomUsername() string {
 	rand.Seed(time.Now().UnixNano())
 	var suffix []string

+ 13 - 0
model/entry.go

@@ -68,6 +68,19 @@ func ValidateDirection(direction string) error {
 	return fmt.Errorf(`Invalid direction, valid direction values are: "asc" or "desc"`)
 }
 
+// ValidateRange makes sure the offset/limit values are valid.
+func ValidateRange(offset, limit int) error {
+	if offset < 0 {
+		return fmt.Errorf(`Offset value should be >= 0`)
+	}
+
+	if limit < 0 {
+		return fmt.Errorf(`Limit value should be >= 0`)
+	}
+
+	return nil
+}
+
 // GetOppositeDirection returns the opposite sorting direction.
 func GetOppositeDirection(direction string) string {
 	if direction == "asc" {

+ 14 - 0
model/entry_test.go

@@ -42,6 +42,20 @@ func TestValidateEntryDirection(t *testing.T) {
 	}
 }
 
+func TestValidateRange(t *testing.T) {
+	if err := ValidateRange(-1, 0); err == nil {
+		t.Error(`An invalid offset should generate a error`)
+	}
+
+	if err := ValidateRange(0, -1); err == nil {
+		t.Error(`An invalid limit should generate a error`)
+	}
+
+	if err := ValidateRange(42, 42); err != nil {
+		t.Error(`A valid offset and limit should not generate any error`)
+	}
+}
+
 func TestGetOppositeDirection(t *testing.T) {
 	if GetOppositeDirection("asc") != "desc" {
 		t.Errorf(`The opposite direction of "asc" should be "desc"`)

+ 61 - 4
server/api/controller/entry.go

@@ -62,13 +62,13 @@ func (c *Controller) GetFeedEntries(ctx *core.Context, request *core.Request, re
 		}
 	}
 
-	order := request.QueryStringParam("order", "id")
+	order := request.QueryStringParam("order", model.DefaultSortingOrder)
 	if err := model.ValidateEntryOrder(order); err != nil {
 		response.JSON().BadRequest(err)
 		return
 	}
 
-	direction := request.QueryStringParam("direction", "desc")
+	direction := request.QueryStringParam("direction", model.DefaultSortingDirection)
 	if err := model.ValidateDirection(direction); err != nil {
 		response.JSON().BadRequest(err)
 		return
@@ -76,12 +76,69 @@ func (c *Controller) GetFeedEntries(ctx *core.Context, request *core.Request, re
 
 	limit := request.QueryIntegerParam("limit", 100)
 	offset := request.QueryIntegerParam("offset", 0)
+	if err := model.ValidateRange(offset, limit); err != nil {
+		response.JSON().BadRequest(err)
+		return
+	}
 
 	builder := c.store.GetEntryQueryBuilder(userID, ctx.UserTimezone())
 	builder.WithFeedID(feedID)
 	builder.WithStatus(status)
-	builder.WithOrder(model.DefaultSortingOrder)
-	builder.WithDirection(model.DefaultSortingDirection)
+	builder.WithOrder(order)
+	builder.WithDirection(direction)
+	builder.WithOffset(offset)
+	builder.WithLimit(limit)
+
+	entries, err := builder.GetEntries()
+	if err != nil {
+		response.JSON().ServerError(errors.New("Unable to fetch the list of entries"))
+		return
+	}
+
+	count, err := builder.CountEntries()
+	if err != nil {
+		response.JSON().ServerError(errors.New("Unable to count the number of entries"))
+		return
+	}
+
+	response.JSON().Standard(&payload.EntriesResponse{Total: count, Entries: entries})
+}
+
+// GetEntries is the API handler to fetch entries.
+func (c *Controller) GetEntries(ctx *core.Context, request *core.Request, response *core.Response) {
+	userID := ctx.UserID()
+
+	status := request.QueryStringParam("status", "")
+	if status != "" {
+		if err := model.ValidateEntryStatus(status); err != nil {
+			response.JSON().BadRequest(err)
+			return
+		}
+	}
+
+	order := request.QueryStringParam("order", model.DefaultSortingOrder)
+	if err := model.ValidateEntryOrder(order); err != nil {
+		response.JSON().BadRequest(err)
+		return
+	}
+
+	direction := request.QueryStringParam("direction", model.DefaultSortingDirection)
+	if err := model.ValidateDirection(direction); err != nil {
+		response.JSON().BadRequest(err)
+		return
+	}
+
+	limit := request.QueryIntegerParam("limit", 100)
+	offset := request.QueryIntegerParam("offset", 0)
+	if err := model.ValidateRange(offset, limit); err != nil {
+		response.JSON().BadRequest(err)
+		return
+	}
+
+	builder := c.store.GetEntryQueryBuilder(userID, ctx.UserTimezone())
+	builder.WithStatus(status)
+	builder.WithOrder(order)
+	builder.WithDirection(direction)
 	builder.WithOffset(offset)
 	builder.WithLimit(limit)
 

+ 1 - 0
server/routes.go

@@ -62,6 +62,7 @@ func getRoutes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Han
 
 	router.Handle("/v1/feeds/{feedID}/entries", apiHandler.Use(apiController.GetFeedEntries)).Methods("GET")
 	router.Handle("/v1/feeds/{feedID}/entries/{entryID}", apiHandler.Use(apiController.GetEntry)).Methods("GET")
+	router.Handle("/v1/entries", apiHandler.Use(apiController.GetEntries)).Methods("GET")
 	router.Handle("/v1/entries", apiHandler.Use(apiController.SetEntryStatus)).Methods("PUT")
 
 	router.Handle("/stylesheets/{name}.css", uiHandler.Use(uiController.Stylesheet)).Name("stylesheet").Methods("GET")

+ 6 - 8
vendor/github.com/lib/pq/.travis.yml

@@ -16,7 +16,7 @@ env:
     - PQGOSSLTESTS=1
     - PQSSLCERTTEST_PATH=$PWD/certs
     - PGHOST=127.0.0.1
-    - MEGACHECK_VERSION=2017.1
+    - MEGACHECK_VERSION=2017.2.1
   matrix:
     - PGVERSION=10
     - PGVERSION=9.6
@@ -46,15 +46,13 @@ script:
   - >
     goimports -d -e $(find -name '*.go') | awk '{ print } END { exit NR == 0 ? 0 : 1 }'
   - go vet ./...
-    # For compatibility with Go 1.5, launch only if megacheck is present,
-    # ignore SA1019 (deprecation warnings) in conn_test.go (we have to use the
-    # deprecated driver.Execer and driver.Queryer interfaces) and S1024
-    # (time.Until) everywhere.
+    # For compatibility with Go 1.5, launch only if megacheck is present.
   - >
-    which megacheck > /dev/null
-    && megacheck -ignore 'github.com/lib/pq/conn_test.go:SA1019 github.com/lib/pq/*.go:S1024' ./...
+    which megacheck > /dev/null && megacheck -go 1.5 ./...
     || echo 'megacheck is not supported, skipping check'
     # For compatibility with Go 1.5, launch only if golint is present.
-  - which golint > /dev/null && golint ./...  || echo 'golint is not supported, skipping check'
+  - >
+    which golint > /dev/null && golint ./...
+    || echo 'golint is not supported, skipping check'
   - PQTEST_BINARY_PARAMETERS=no  go test -race -v ./...
   - PQTEST_BINARY_PARAMETERS=yes go test -race -v ./...

+ 49 - 26
vendor/github.com/miniflux/miniflux-go/client.go

@@ -196,7 +196,7 @@ func (c *Client) Feeds() (Feeds, error) {
 	return feeds, nil
 }
 
-// Feed gets a new feed.
+// Feed gets a feed.
 func (c *Client) Feed(feedID int64) (*Feed, error) {
 	body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d", feedID))
 	if err != nil {
@@ -291,35 +291,28 @@ func (c *Client) Entry(feedID, entryID int64) (*Entry, error) {
 	return entry, nil
 }
 
-// Entries gets feed entries.
-func (c *Client) Entries(feedID int64, filter *Filter) (*EntryResultSet, error) {
-	path := fmt.Sprintf("/v1/feeds/%d/entries", feedID)
+// Entries fetch entries.
+func (c *Client) Entries(filter *Filter) (*EntryResultSet, error) {
+	path := buildFilterQueryString("/v1/entries", filter)
 
-	if filter != nil {
-		values := url.Values{}
-
-		if filter.Status != "" {
-			values.Set("status", filter.Status)
-		}
-
-		if filter.Direction != "" {
-			values.Set("direction", filter.Direction)
-		}
-
-		if filter.Order != "" {
-			values.Set("order", filter.Order)
-		}
+	body, err := c.request.Get(path)
+	if err != nil {
+		return nil, err
+	}
+	defer body.Close()
 
-		if filter.Limit != 0 {
-			values.Set("limit", strconv.Itoa(filter.Limit))
-		}
+	var result EntryResultSet
+	decoder := json.NewDecoder(body)
+	if err := decoder.Decode(&result); err != nil {
+		return nil, fmt.Errorf("miniflux: response error (%v)", err)
+	}
 
-		if filter.Offset != 0 {
-			values.Set("offset", strconv.Itoa(filter.Offset))
-		}
+	return &result, nil
+}
 
-		path = fmt.Sprintf("%s?%s", path, values.Encode())
-	}
+// FeedEntries fetch feed entries.
+func (c *Client) FeedEntries(feedID int64, filter *Filter) (*EntryResultSet, error) {
+	path := buildFilterQueryString(fmt.Sprintf("/v1/feeds/%d/entries", feedID), filter)
 
 	body, err := c.request.Get(path)
 	if err != nil {
@@ -356,3 +349,33 @@ func (c *Client) UpdateEntries(entryIDs []int64, status string) error {
 func NewClient(endpoint, username, password string) *Client {
 	return &Client{request: &request{endpoint: endpoint, username: username, password: password}}
 }
+
+func buildFilterQueryString(path string, filter *Filter) string {
+	if filter != nil {
+		values := url.Values{}
+
+		if filter.Status != "" {
+			values.Set("status", filter.Status)
+		}
+
+		if filter.Direction != "" {
+			values.Set("direction", filter.Direction)
+		}
+
+		if filter.Order != "" {
+			values.Set("order", filter.Order)
+		}
+
+		if filter.Limit >= 0 {
+			values.Set("limit", strconv.Itoa(filter.Limit))
+		}
+
+		if filter.Offset >= 0 {
+			values.Set("offset", strconv.Itoa(filter.Offset))
+		}
+
+		path = fmt.Sprintf("%s?%s", path, values.Encode())
+	}
+
+	return path
+}