~netlandish/links

af61e360d268666f0bc3db853c985f636e664b5a — Yader Velasquez 9 months ago 1cba065
Add more test coverage
6 files changed, 246 insertions(+), 44 deletions(-)

M analytics/routes.go
M analytics/routes_test.go
R analytics/samples/{analytics.json => analytics_lists.json}
A analytics/samples/analytics_shorts.json
M core/routes_test.go
A core/samples/org_list.json
M analytics/routes.go => analytics/routes.go +0 -32
@@ 27,7 27,6 @@ type Service struct {

func (s *Service) RegisterRoutes() {
	s.eg.Use(auth.AuthRequired())
	s.eg.GET("/:type", s.List).Name = s.RouteName("list")
	s.eg.GET("/:type/:id", s.Detail).Name = s.RouteName("detail")
}



@@ 236,37 235,6 @@ func (s *Service) Detail(c echo.Context) error {
	return gctx.Render(http.StatusOK, "analytics_detail.html", gmap)
}

func (s *Service) List(c echo.Context) error {
	aType := c.Param("type")
	var list interface{}
	var err error
	switch aType {
	case "links":
		list, err = models.GetOrgLinksAnalytics(c.Request().Context(), nil)
		if err != nil {
			return err
		}
	case "shorts":
		list, err = models.GetLinkShortsAnalytics(c.Request().Context(), nil)
		if err != nil {
			return err
		}
	case "lists":
		list, err = models.GetListsAnalytics(c.Request().Context(), nil)
		if err != nil {
			return err
		}

	default:
		return echo.NotFoundHandler(c)
	}
	gctx := c.(*server.Context)
	lt := localizer.GetSessionLocalizer(c)
	pd := localizer.NewPageData(lt.Translate(""))
	gmap := gobwebs.Map{"pd": pd, "list": list, "aType": aType}
	return gctx.Render(http.StatusOK, "analytics_list.html", gmap)
}

func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
}

M analytics/routes_test.go => analytics/routes_test.go +45 -12
@@ 7,6 7,7 @@ import (
	"links/models"
	"net/http"
	"net/http/httptest"
	"net/url"
	"sort"
	"testing"
	"time"


@@ 30,13 31,13 @@ func TestHandler(t *testing.T) {
	go srv.Run()
	dbCtx := cmd.NewDBContext(srv.DB, "America/Managua")

	httpmock.Activate()
	defer httpmock.DeactivateAndReset()
	jsonResponse, err := httpmock.NewJsonResponder(http.StatusOK, httpmock.File("samples/analytics.json"))
	c.NoError(err)
	httpmock.RegisterResponder("POST", "http://127.0.0.1:8080/query", jsonResponse)
	t.Run("analytics short details", func(t *testing.T) {
		httpmock.Activate()
		defer httpmock.DeactivateAndReset()
		jsonResponse, err := httpmock.NewJsonResponder(http.StatusOK, httpmock.File("samples/analytics_shorts.json"))
		c.NoError(err)

	t.Run("checkout success", func(t *testing.T) {
		httpmock.RegisterResponder("POST", "http://127.0.0.1:8080/query", jsonResponse)
		short := &models.LinkShort{
			Title:     "Testing short",
			URL:       "http://testing.com",


@@ 45,12 46,7 @@ func TestHandler(t *testing.T) {
			UserID:    int(user.ID),
			DomainID:  1,
		}
		err := short.Store(dbCtx)
		c.NoError(err)
		req := httptest.NewRequest(http.MethodGet, "/", nil)

		// Add fake analytics entries
		err = analytics.AddAnalytics(dbCtx, req, short.ID, analytics.LinkShortAnalyticsFilter, "")
		err = short.Store(dbCtx)
		c.NoError(err)
		request := httptest.NewRequest(http.MethodGet, "/shorts/1", nil)
		request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)


@@ 67,6 63,43 @@ func TestHandler(t *testing.T) {
		c.NoError(err)
		c.Equal(http.StatusOK, recorder.Code)
	})

	t.Run("analytics list detail", func(t *testing.T) {
		httpmock.Activate()
		defer httpmock.DeactivateAndReset()
		jsonResponse, err := httpmock.NewJsonResponder(http.StatusOK, httpmock.File("samples/analytics_lists.json"))
		c.NoError(err)

		httpmock.RegisterResponder("POST", "http://127.0.0.1:8080/query", jsonResponse)
		list := &models.Listing{
			Title:    "Testing lists",
			Slug:     "testing-list",
			OrgID:    1,
			UserID:   int(user.ID),
			DomainID: 1,
		}
		err = list.Store(dbCtx)
		c.NoError(err)

		q := make(url.Values)
		q.Add("date-start", "2023-12-12")
		q.Add("date-end", "2024-02-12")
		request := httptest.NewRequest(http.MethodGet, "/lists/1?"+q.Encode(), nil)
		request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
		recorder := httptest.NewRecorder()
		ctx := &server.Context{
			Server:  srv,
			Context: e.NewContext(request, recorder),
			User:    user,
		}
		ctx.SetPath("/:slug/analytics/:type/:id")
		ctx.SetParamNames("slug", "type", "id")
		ctx.SetParamValues("personal-org", "lists", fmt.Sprint(list.ID))
		err = cmd.MakeRequest(srv, analyticsService.Detail, ctx)
		c.NoError(err)
		c.Equal(http.StatusOK, recorder.Code)

	})
}

func TestAPI(t *testing.T) {

R analytics/samples/analytics.json => analytics/samples/analytics_lists.json +0 -0
A analytics/samples/analytics_shorts.json => analytics/samples/analytics_shorts.json +61 -0
@@ 0,0 1,61 @@
{
    "data": {
        "analytics": {
            "title": "New Short",
            "clicksOverTime": [
                {
                    "key": "2024-01-21",
                    "val": 10
                },
                {
                    "key": "2024-01-28",
                    "val": 10
                },
                {
                    "key": "2024-02-04",
                    "val": 10
                },
                {
                    "key": "2024-02-11",
                    "val": 10
                },
                {
                    "key": "2024-01-14",
                    "val": 10
                }
            ],
            "shortLink": [
                {
                    "id": 12,
                    "title": "Title",
                    "clicks": 12
                }
            ],
            "clicksCountry": [
                {
                    "key": "Country",
                    "val": 1000
                }
            ],
            "clicksCity": [
                {
                    "key": "City",
                    "val": 1000
                }

            ],
            "clicksDevice": [
                {
                    "key": "Device",
                    "val": 1000
                }
            ],
            "clicksReferrer": [
                {
                    "key": "Referrer",
                    "val": 1000
                }
            ]
        }
    }
}

M core/routes_test.go => core/routes_test.go +101 -0
@@ 2,6 2,7 @@ package core_test

import (
	"database/sql"
	"fmt"
	"links/cmd"
	"links/core"
	"links/models"


@@ 35,6 36,77 @@ func TestHandlers(t *testing.T) {
	})
	c.NoError(err)

	t.Run("invalid domain", func(t *testing.T) {
		invalidD := &models.Domain{
			Name:       "invalid",
			LookupName: "foo.com",
			OrgID:      sql.NullInt64{Valid: true, Int64: 1},
			Level:      models.DomainLevelUser,
			Service:    models.DomainServiceLinks,
			Status:     models.DomainStatusApproved,
			IsActive:   false,
		}
		err := invalidD.Store(dbCtx)
		c.NoError(err)

		request := httptest.NewRequest(http.MethodGet, "/invalid/"+invalidD.LookupName, nil)
		recorder := httptest.NewRecorder()
		ctx := &server.Context{
			Server:  srv,
			Context: e.NewContext(request, recorder),
			User:    loggedInUser,
		}
		ctx.SetPath("/invalid/:d")
		ctx.SetParamNames("d")
		ctx.SetParamValues(invalidD.LookupName)
		err = cmd.MakeRequest(srv, coreService.InvalidDomain, ctx)
		c.NoError(err)
		htmlBody := recorder.Body.String()
		c.True(strings.Contains(htmlBody, "Invalid Domain"))

	})

	t.Run("homepage", func(t *testing.T) {
		d := &models.Domain{
			Name:       "valid user domain",
			LookupName: "foo.com",
			OrgID:      sql.NullInt64{Valid: false, Int64: 0},
			Level:      models.DomainLevelSystem,
			Service:    models.DomainServiceLinks,
			Status:     models.DomainStatusApproved,
			IsActive:   true,
		}
		err := d.Store(dbCtx)
		c.NoError(err)
		request := httptest.NewRequest(http.MethodGet, "/", nil)
		recorder := httptest.NewRecorder()
		ctx := &server.Context{
			Server:  srv,
			Context: e.NewContext(request, recorder),
			User:    loggedInUser,
		}
		ctx.SetPath("/")
		err = cmd.MakeRequestWithDomain(srv, coreService.Homepage, ctx, d)
		c.NoError(err)
	})

	t.Run("feature tour", func(t *testing.T) {
		request := httptest.NewRequest(http.MethodGet, "/tour", nil)
		recorder := httptest.NewRecorder()
		ctx := &server.Context{
			Server:  srv,
			Context: e.NewContext(request, recorder),
			User:    loggedInUser,
		}
		ctx.SetPath("/tour")
		err = cmd.MakeRequestWithDomain(srv, coreService.FeatureTour, ctx, domains[0])
		c.NoError(err)
		c.Equal(http.StatusOK, recorder.Code)
		htmlBody := recorder.Body.String()
		c.True(strings.Contains(htmlBody, "Explore Features"))

	})

	t.Run("home link list", func(t *testing.T) {
		httpmock.Activate()
		defer httpmock.DeactivateAndReset()


@@ 387,6 459,35 @@ func TestHandlers(t *testing.T) {
		c.Equal(http.StatusMovedPermanently, recorder.Code)
	})

	t.Run("org lists", func(t *testing.T) {
		domains, err := models.GetDomains(dbCtx, &database.FilterOptions{
			Filter: sq.And{
				sq.Eq{"d.lookup_name": "foo.com"},
			},
			Limit: 1,
		})
		c.NoError(err)
		domain := domains[0]
		httpmock.Activate()
		defer httpmock.DeactivateAndReset()
		jsonResponse, err := httpmock.NewJsonResponder(http.StatusOK,
			httpmock.File("samples/org_list.json"))
		c.NoError(err)
		httpmock.RegisterResponder("POST", "http://127.0.0.1:8080/query", jsonResponse)

		request := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/organizations?d=%s", domain.LookupName), nil)
		recorder := httptest.NewRecorder()
		ctx := &server.Context{
			Server:  srv,
			Context: e.NewContext(request, recorder),
			User:    loggedInUser,
		}
		ctx.SetPath("/organizations")
		err = cmd.MakeRequest(srv, coreService.OrgList, ctx)
		c.NoError(err)
		c.Equal(http.StatusOK, recorder.Code)
	})

	t.Run("qr redirect", func(t *testing.T) {
		httpmock.Activate()
		defer httpmock.DeactivateAndReset()

A core/samples/org_list.json => core/samples/org_list.json +39 -0
@@ 0,0 1,39 @@
{
    "data": {
        "getOrganizations": [
            {
                "id": 1,
                "name": "Org 1",
                "slug": "org-1",
                "isActive": true,
                "settings": {
                    "billing": {
                        "status": 1
                    }
                }
            },
            {
                "id": 59,
                "name": "Org 2",
                "slug": "org-2",
                "isActive": true,
                "settings": {
                    "billing": {
                        "status": 2
                    }
                }
            },
            {
                "id": 60,
                "name": "Org 3",
                "slug": "org-3",
                "isActive": true,
                "settings": {
                    "billing": {
                        "status": 0
                    }
                }
            }
        ]
    }
}