Add integration tests for entries

This commit is contained in:
Frédéric Guillot 2017-11-26 15:07:59 -08:00
parent 51f7775466
commit 8781648af9
9 changed files with 356 additions and 43 deletions

4
Gopkg.lock generated
View file

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

View file

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

View file

@ -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 { func getRandomUsername() string {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
var suffix []string var suffix []string

View file

@ -68,6 +68,19 @@ func ValidateDirection(direction string) error {
return fmt.Errorf(`Invalid direction, valid direction values are: "asc" or "desc"`) 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. // GetOppositeDirection returns the opposite sorting direction.
func GetOppositeDirection(direction string) string { func GetOppositeDirection(direction string) string {
if direction == "asc" { if direction == "asc" {

View file

@ -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) { func TestGetOppositeDirection(t *testing.T) {
if GetOppositeDirection("asc") != "desc" { if GetOppositeDirection("asc") != "desc" {
t.Errorf(`The opposite direction of "asc" should be "desc"`) t.Errorf(`The opposite direction of "asc" should be "desc"`)

View file

@ -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 { if err := model.ValidateEntryOrder(order); err != nil {
response.JSON().BadRequest(err) response.JSON().BadRequest(err)
return return
} }
direction := request.QueryStringParam("direction", "desc") direction := request.QueryStringParam("direction", model.DefaultSortingDirection)
if err := model.ValidateDirection(direction); err != nil { if err := model.ValidateDirection(direction); err != nil {
response.JSON().BadRequest(err) response.JSON().BadRequest(err)
return return
@ -76,12 +76,69 @@ func (c *Controller) GetFeedEntries(ctx *core.Context, request *core.Request, re
limit := request.QueryIntegerParam("limit", 100) limit := request.QueryIntegerParam("limit", 100)
offset := request.QueryIntegerParam("offset", 0) 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 := c.store.GetEntryQueryBuilder(userID, ctx.UserTimezone())
builder.WithFeedID(feedID) builder.WithFeedID(feedID)
builder.WithStatus(status) builder.WithStatus(status)
builder.WithOrder(model.DefaultSortingOrder) builder.WithOrder(order)
builder.WithDirection(model.DefaultSortingDirection) 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.WithOffset(offset)
builder.WithLimit(limit) builder.WithLimit(limit)

View file

@ -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", apiHandler.Use(apiController.GetFeedEntries)).Methods("GET")
router.Handle("/v1/feeds/{feedID}/entries/{entryID}", apiHandler.Use(apiController.GetEntry)).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("/v1/entries", apiHandler.Use(apiController.SetEntryStatus)).Methods("PUT")
router.Handle("/stylesheets/{name}.css", uiHandler.Use(uiController.Stylesheet)).Name("stylesheet").Methods("GET") router.Handle("/stylesheets/{name}.css", uiHandler.Use(uiController.Stylesheet)).Name("stylesheet").Methods("GET")

14
vendor/github.com/lib/pq/.travis.yml generated vendored
View file

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

View file

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