grafana/pkg/services/rendering/rendering_test.go
Artur Wierzbicki a025109647
Dash previews: populate crawler queue from SQL query (#44083)
* add SQL migrations

* dashboard previews from sql: poc

* added todos

* refactor: use the same enums where possible

* use useEffect, always return json

* added todo

* refactor + delete files after use

* refactor + fix manual thumbnail upload

* refactor: move all interactions with sqlStore to thumbnail repo

* refactor: remove file operations in thumb crawler/service

* refactor: fix dashboard_thumbs sql store

* refactor: extracted thumbnail fetching/updating to a hook

* refactor: store thumbnails in redux store

* refactor: store thumbnails in redux store

* refactor: private'd repo methods

* removed redux storage, saving images as blobs

* allow for configurable rendering timeouts

* added 1) query for dashboards with stale thumbnails, 2) command for marking thumbnails as stale

* use sql-based queue in crawler

* ui for marking thumbnails as stale

* replaced `stale` boolean prop with `state` enum

* introduce rendering session

* compilation errors

* fix crawler stop button

* rename thumbnail state frozen to locked

* #44449: fix merge conflicts

* #44449: remove thumb methods from `Store` interface

* #44449: clean filepath, defer file closing

* #44449: fix rendering.Theme cyclic import

* #44449: linting

* #44449: linting

* #44449: mutex'd crawlerStatus access

* #44449: added integration tests for `sqlstore.dashboard_thumbs`

* #44449: added comments to explain the `ThumbnailState` enum

* #44449: use os.ReadFile rather then os.Open

* #44449: always enable dashboardPreviews feature during integration tests

* #44449: remove sleep time, adjust number of threads

* #44449: review fix: add `orgId` to `DashboardThumbnailMeta`

* #44449: review fix: automatic parsing of thumbnailState

* #44449: lint fixes

* #44449: review fix: prefer `WithDbSession` over `WithTransactionalDbSession`

* #44449: review fix: add a comment explaining source of the filepath

* #44449: review fix: added filepath validation

* #44449: review fixes https://github.com/grafana/grafana/pull/45063/files @fzambia

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
Co-authored-by: Alexander Emelin <frvzmb@gmail.com>
2022-02-09 13:23:32 +04:00

213 lines
6.2 KiB
Go

package rendering
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetUrl(t *testing.T) {
path := "render/d-solo/5SdHCadmz/panel-tests-graph?orgId=1&from=1587390211965&to=1587393811965&panelId=5&width=1000&height=500&tz=Europe%2FStockholm"
cfg := setting.NewCfg()
rs := &RenderingService{
Cfg: cfg,
}
t.Run("When renderer and callback url configured should return callback url plus path", func(t *testing.T) {
rs.Cfg.RendererUrl = "http://localhost:8081/render"
rs.Cfg.RendererCallbackUrl = "http://public-grafana.com/"
url := rs.getURL(path)
require.Equal(t, rs.Cfg.RendererCallbackUrl+path+"&render=1", url)
})
t.Run("When renderer url not configured", func(t *testing.T) {
rs.Cfg.RendererUrl = ""
rs.domain = "localhost"
rs.Cfg.HTTPPort = "3000"
t.Run("And protocol HTTP configured should return expected path", func(t *testing.T) {
rs.Cfg.ServeFromSubPath = false
rs.Cfg.AppSubURL = ""
rs.Cfg.Protocol = setting.HTTPScheme
url := rs.getURL(path)
require.Equal(t, "http://localhost:3000/"+path+"&render=1", url)
t.Run("And serve from sub path should return expected path", func(t *testing.T) {
rs.Cfg.ServeFromSubPath = true
rs.Cfg.AppSubURL = "/grafana"
url := rs.getURL(path)
require.Equal(t, "http://localhost:3000/grafana/"+path+"&render=1", url)
})
})
t.Run("And protocol HTTPS configured should return expected path", func(t *testing.T) {
rs.Cfg.ServeFromSubPath = false
rs.Cfg.AppSubURL = ""
rs.Cfg.Protocol = setting.HTTPSScheme
url := rs.getURL(path)
require.Equal(t, "https://localhost:3000/"+path+"&render=1", url)
})
t.Run("And protocol HTTP2 configured should return expected path", func(t *testing.T) {
rs.Cfg.ServeFromSubPath = false
rs.Cfg.AppSubURL = ""
rs.Cfg.Protocol = setting.HTTP2Scheme
url := rs.getURL(path)
require.Equal(t, "https://localhost:3000/"+path+"&render=1", url)
})
})
}
func TestRenderErrorImage(t *testing.T) {
path, err := filepath.Abs("../../../")
require.NoError(t, err)
rs := RenderingService{
Cfg: &setting.Cfg{
HomePath: path,
},
}
t.Run("No theme set returns error image with dark theme", func(t *testing.T) {
result, err := rs.RenderErrorImage("", nil)
require.NoError(t, err)
assert.Equal(t, result.FilePath, path+"/public/img/rendering_error_dark.png")
})
t.Run("Timeout error returns timeout error image", func(t *testing.T) {
result, err := rs.RenderErrorImage(models.ThemeLight, ErrTimeout)
require.NoError(t, err)
assert.Equal(t, result.FilePath, path+"/public/img/rendering_timeout_light.png")
})
t.Run("Generic error returns error image", func(t *testing.T) {
result, err := rs.RenderErrorImage(models.ThemeLight, errors.New("an error"))
require.NoError(t, err)
assert.Equal(t, result.FilePath, path+"/public/img/rendering_error_light.png")
})
t.Run("Unknown image path returns error", func(t *testing.T) {
result, err := rs.RenderErrorImage("abc", errors.New("random error"))
assert.Error(t, err)
assert.Nil(t, result)
})
}
func TestRenderLimitImage(t *testing.T) {
path, err := filepath.Abs("../../../")
require.NoError(t, err)
rs := RenderingService{
Cfg: &setting.Cfg{
HomePath: path,
},
inProgressCount: 2,
log: log.New("test"),
}
tests := []struct {
name string
theme models.Theme
expected string
}{
{
name: "Light theme returns light image",
theme: models.ThemeLight,
expected: path + "/public/img/rendering_limit_light.png",
},
{
name: "Dark theme returns dark image",
theme: models.ThemeDark,
expected: path + "/public/img/rendering_limit_dark.png",
},
{
name: "No theme returns dark image",
theme: "",
expected: path + "/public/img/rendering_limit_dark.png",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
opts := Opts{Theme: tc.theme, ConcurrentLimit: 1}
result, err := rs.Render(context.Background(), opts, nil)
assert.NoError(t, err)
assert.Equal(t, tc.expected, result.FilePath)
})
}
}
func TestRenderingServiceGetRemotePluginVersion(t *testing.T) {
cfg := setting.NewCfg()
rs := &RenderingService{
Cfg: cfg,
log: log.New("rendering-test"),
}
t.Run("When renderer responds with correct version should return that version", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("{\"version\":\"2.7.1828\"}"))
require.NoError(t, err)
}))
defer server.Close()
rs.Cfg.RendererUrl = server.URL + "/render"
version, err := rs.getRemotePluginVersion()
require.NoError(t, err)
require.Equal(t, "2.7.1828", version)
})
t.Run("When renderer responds with 404 should assume a valid but old version", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
defer server.Close()
rs.Cfg.RendererUrl = server.URL + "/render"
version, err := rs.getRemotePluginVersion()
require.NoError(t, err)
require.Equal(t, version, "1.0.0")
})
t.Run("When renderer responds with 500 should retry until success", func(t *testing.T) {
tries := uint(0)
ctx, cancel := context.WithCancel(context.Background())
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
tries++
if tries < remoteVersionFetchRetries {
w.WriteHeader(http.StatusInternalServerError)
} else {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("{\"version\":\"3.1.4159\"}"))
require.NoError(t, err)
cancel()
}
}))
defer server.Close()
rs.Cfg.RendererUrl = server.URL + "/render"
remoteVersionFetchInterval = time.Millisecond
remoteVersionFetchRetries = 5
go func() {
require.NoError(t, rs.Run(ctx))
}()
require.Eventually(t, func() bool { return rs.Version() == "3.1.4159" }, time.Second, time.Millisecond)
})
}