Skip to content

Commit

Permalink
fix: badges broken (resolve #475)
Browse files Browse the repository at this point in the history
  • Loading branch information
muety committed Mar 15, 2023
1 parent 46a248a commit c9f2518
Show file tree
Hide file tree
Showing 13 changed files with 240 additions and 53 deletions.
47 changes: 47 additions & 0 deletions mocks/summary_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package mocks

import (
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/models/types"
"github.com/stretchr/testify/mock"
"time"
)

type SummaryServiceMock struct {
mock.Mock
}

func (m *SummaryServiceMock) Aliased(t time.Time, t2 time.Time, u *models.User, r types.SummaryRetriever, f *models.Filters, b bool) (*models.Summary, error) {
args := m.Called(t, t2, u, r, f)
return args.Get(0).(*models.Summary), args.Error(1)
}

func (m *SummaryServiceMock) Retrieve(t time.Time, t2 time.Time, u *models.User, f *models.Filters) (*models.Summary, error) {
args := m.Called(t, t2, u, f)
return args.Get(0).(*models.Summary), args.Error(1)
}

func (m *SummaryServiceMock) Summarize(t time.Time, t2 time.Time, u *models.User, f *models.Filters) (*models.Summary, error) {
args := m.Called(t, t2, u, f)
return args.Get(0).(*models.Summary), args.Error(1)
}

func (m *SummaryServiceMock) GetLatestByUser() ([]*models.TimeByUser, error) {
args := m.Called()
return args.Get(0).([]*models.TimeByUser), args.Error(1)
}

func (m *SummaryServiceMock) DeleteByUser(s string) error {
args := m.Called(s)
return args.Error(0)
}

func (m *SummaryServiceMock) DeleteByUserBefore(s string, t time.Time) error {
args := m.Called(s, t)
return args.Error(0)
}

func (m *SummaryServiceMock) Insert(s *models.Summary) error {
args := m.Called(s)
return args.Error(0)
}
8 changes: 8 additions & 0 deletions models/types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package types

import (
"github.com/muety/wakapi/models"
"time"
)

type SummaryRetriever func(f, t time.Time, u *models.User, filters *models.Filters) (*models.Summary, error)
2 changes: 1 addition & 1 deletion routes/api/badge.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func NewBadgeHandler(userService services.IUserService, summaryService services.
}

func (h *BadgeHandler) RegisterRoutes(router chi.Router) {
router.Get("/badge/{user}", h.Get)
router.Get("/badge/{user}/*", h.Get)
}

func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
Expand Down
154 changes: 154 additions & 0 deletions routes/api/badge_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package api

import (
"github.com/go-chi/chi/v5"
"github.com/muety/wakapi/middlewares"
"github.com/muety/wakapi/mocks"
"github.com/muety/wakapi/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"io/ioutil"
"net/http"
"net/http/httptest"
"regexp"
"strings"
"testing"
"time"
)

var (
user1 = models.User{
ID: "user1",
ShareDataMaxDays: 30,
ShareLanguages: true,
}

summary1 = models.Summary{
User: &user1,
UserID: "user1",
FromTime: models.CustomTime(time.Date(2023, 3, 14, 0, 0, 0, 0, time.Local)),
ToTime: models.CustomTime(time.Date(2023, 3, 14, 23, 59, 59, 0, time.Local)),
Languages: []*models.SummaryItem{
{
Type: models.SummaryLanguage,
Key: "go",
Total: 12 * time.Minute / time.Second,
},
},
}
)

func TestBadgeHandler_Get(t *testing.T) {
router := chi.NewRouter()
apiRouter := chi.NewRouter()
apiRouter.Use(middlewares.NewPrincipalMiddleware())
router.Mount("/api", apiRouter)

userServiceMock := new(mocks.UserServiceMock)
userServiceMock.On("GetUserById", "user1").Return(&user1, nil)

summaryServiceMock := new(mocks.SummaryServiceMock)
summaryServiceMock.On("Aliased", mock.AnythingOfType("time.Time"), mock.AnythingOfType("time.Time"), &user1, mock.Anything, mock.Anything).Return(&summary1, nil)

badgeHandler := NewBadgeHandler(userServiceMock, summaryServiceMock)
badgeHandler.RegisterRoutes(apiRouter)

t.Run("when requesting badge", func(t *testing.T) {
t.Run("should return badge", func(t *testing.T) {
rec := httptest.NewRecorder()

req := httptest.NewRequest(http.MethodGet, "/api/badge/{user}/interval:week/language:go", nil)
req = withUrlParam(req, "user", "user1")

router.ServeHTTP(rec, req)
res := rec.Result()
defer res.Body.Close()

assert.Equal(t, http.StatusOK, res.StatusCode)

data, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Errorf("unextected error. Error: %s", err)
}

assert.True(t, strings.HasPrefix(string(data), "<svg"))
assert.Contains(t, string(data), "0 hrs 12 mins")
})

t.Run("should not return badge if shared interval exceeded", func(t *testing.T) {
rec := httptest.NewRecorder()

req := httptest.NewRequest(http.MethodGet, "/api/badge/{user}/interval:year/language:go", nil)
req = withUrlParam(req, "user", "user1")

router.ServeHTTP(rec, req)
res := rec.Result()
defer res.Body.Close()

assert.Equal(t, http.StatusForbidden, res.StatusCode)

data, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Errorf("unextected error. Error: %s", err)
}

assert.False(t, strings.HasPrefix(string(data), "<svg"))
})

t.Run("should not return badge if entity type not shared", func(t *testing.T) {
rec := httptest.NewRecorder()

req := httptest.NewRequest(http.MethodGet, "/api/badge/{user}/interval:year/project:foo", nil)
req = withUrlParam(req, "user", "user1")

router.ServeHTTP(rec, req)
res := rec.Result()
defer res.Body.Close()

assert.Equal(t, http.StatusForbidden, res.StatusCode)

data, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Errorf("unextected error. Error: %s", err)
}

assert.False(t, strings.HasPrefix(string(data), "<svg"))
})
})
}

func TestBadgeHandler_EntityPattern(t *testing.T) {
type test struct {
test string
key string
val string
}

pathPrefix := "/compat/shields/v1/current/today/"

tests := []test{
{test: pathPrefix + "project:wakapi", key: "project", val: "wakapi"},
{test: pathPrefix + "os:Linux", key: "os", val: "Linux"},
{test: pathPrefix + "editor:VSCode", key: "editor", val: "VSCode"},
{test: pathPrefix + "language:Java", key: "language", val: "Java"},
{test: pathPrefix + "machine:devmachine", key: "machine", val: "devmachine"},
{test: pathPrefix + "label:work", key: "label", val: "work"},
{test: pathPrefix + "foo:bar", key: "", val: ""}, // invalid entity
{test: pathPrefix + "project:01234", key: "project", val: "01234"}, // digits only
{test: pathPrefix + "project:anchr-web-ext", key: "project", val: "anchr-web-ext"}, // with dashes
{test: pathPrefix + "project:wakapi v2", key: "project", val: "wakapi v2"}, // with blank space
{test: pathPrefix + "project:project", key: "project", val: "project"},
{test: pathPrefix + "project:Anchr-Android_v2.0", key: "project", val: "Anchr-Android_v2.0"}, // all the way
}

sut := regexp.MustCompile(`(project|os|editor|language|machine|label):([^:?&/]+)`) // see entityFilterPattern in badge_utils.go

for _, tc := range tests {
var key, val string
if groups := sut.FindStringSubmatch(tc.test); len(groups) > 2 {
key, val = groups[1], groups[2]
}
assert.Equal(t, tc.key, key)
assert.Equal(t, tc.val, val)
}
}
17 changes: 17 additions & 0 deletions routes/api/test_utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package api

import (
"context"
"github.com/go-chi/chi/v5"
"net/http"
"strings"
)

func withUrlParam(r *http.Request, key, value string) *http.Request {
r.URL.RawPath = strings.Replace(r.URL.RawPath, "{"+key+"}", value, 1)
r.URL.Path = strings.Replace(r.URL.Path, "{"+key+"}", value, 1)
rctx := chi.NewRouteContext()
rctx.URLParams.Add(key, value)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
return r
}
3 changes: 2 additions & 1 deletion routes/compat/shields/v1/badge.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"github.com/go-chi/chi/v5"
"github.com/muety/wakapi/helpers"
"github.com/muety/wakapi/models/types"
routeutils "github.com/muety/wakapi/routes/utils"
"net/http"
"time"
Expand Down Expand Up @@ -97,7 +98,7 @@ func (h *BadgeHandler) loadUserSummary(user *models.User, interval *models.Inter
User: user,
}

var retrieveSummary services.SummaryRetriever = h.summarySrvc.Retrieve
var retrieveSummary types.SummaryRetriever = h.summarySrvc.Retrieve
if summaryParams.Recompute {
retrieveSummary = h.summarySrvc.Summarize
}
Expand Down
43 changes: 0 additions & 43 deletions routes/compat/shields/v1/badge_test.go

This file was deleted.

3 changes: 2 additions & 1 deletion routes/compat/wakatime/v1/all_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/muety/wakapi/middlewares"
"github.com/muety/wakapi/models"
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
"github.com/muety/wakapi/models/types"
routeutils "github.com/muety/wakapi/routes/utils"
"github.com/muety/wakapi/services"
"net/http"
Expand Down Expand Up @@ -68,7 +69,7 @@ func (h *AllTimeHandler) loadUserSummary(user *models.User, filters *models.Filt
Recompute: false,
}

var retrieveSummary services.SummaryRetriever = h.summarySrvc.Retrieve
var retrieveSummary types.SummaryRetriever = h.summarySrvc.Retrieve
if summaryParams.Recompute {
retrieveSummary = h.summarySrvc.Summarize
}
Expand Down
3 changes: 2 additions & 1 deletion routes/compat/wakatime/v1/statusbar.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1
import (
"github.com/go-chi/chi/v5"
"github.com/muety/wakapi/helpers"
"github.com/muety/wakapi/models/types"
"net/http"
"time"

Expand Down Expand Up @@ -90,7 +91,7 @@ func (h *StatusBarHandler) loadUserSummary(user *models.User, start, end time.Ti
Recompute: false,
}

var retrieveSummary services.SummaryRetriever = h.summarySrvc.Retrieve
var retrieveSummary types.SummaryRetriever = h.summarySrvc.Retrieve
if summaryParams.Recompute {
retrieveSummary = h.summarySrvc.Summarize
}
Expand Down
2 changes: 1 addition & 1 deletion routes/compat/wakatime/v1/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestUsersHandler_Get(t *testing.T) {

data, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Errorf("unextected error. Error: %s", err)
t.Errorf("unexpected error. Error: %s", err)
}

if !strings.Contains(string(data), "\"username\":\"AdminUser\"") {
Expand Down
3 changes: 2 additions & 1 deletion routes/utils/summary_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package utils
import (
"github.com/muety/wakapi/helpers"
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/models/types"
"github.com/muety/wakapi/services"
"net/http"
"strings"
Expand All @@ -17,7 +18,7 @@ func LoadUserSummary(ss services.ISummaryService, r *http.Request) (*models.Summ
}

func LoadUserSummaryByParams(ss services.ISummaryService, params *models.SummaryParams) (*models.Summary, error, int) {
var retrieveSummary services.SummaryRetriever = ss.Retrieve
var retrieveSummary types.SummaryRetriever = ss.Retrieve
if params.Recompute {
retrieveSummary = ss.Summarize
}
Expand Down
3 changes: 2 additions & 1 deletion services/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package services
import (
datastructure "github.com/duke-git/lancet/v2/datastructure/set"
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/models/types"
"github.com/muety/wakapi/utils"
"time"
)
Expand Down Expand Up @@ -88,7 +89,7 @@ type IDurationService interface {
}

type ISummaryService interface {
Aliased(time.Time, time.Time, *models.User, SummaryRetriever, *models.Filters, bool) (*models.Summary, error)
Aliased(time.Time, time.Time, *models.User, types.SummaryRetriever, *models.Filters, bool) (*models.Summary, error)
Retrieve(time.Time, time.Time, *models.User, *models.Filters) (*models.Summary, error)
Summarize(time.Time, time.Time, *models.User, *models.Filters) (*models.Summary, error)
GetLatestByUser() ([]*models.TimeByUser, error)
Expand Down
Loading

0 comments on commit c9f2518

Please sign in to comment.