Hackaton: Add more unit tests, take 3 (#101525)

* serviceaccounts/secretscan: test Service more thoroughly

* middleware/cookies: add tests for CookieOptions

* anonymous/anonimpl: cover a couple more methods

* components/imguploader: Implement WebDAV integration tests

* components/apikeygen: also check IsValid method

* bus: cover invalid callback signature cases

* cloudmigration/objectstorage: add basic unit tests

* login/social/connectors: add test case for GitHub OAuth fetch emails+orgs

* expr/classic: cover more evaluator types in tests
pull/101606/head^2
Matheus Macabu 3 months ago committed by GitHub
parent dc2defd84f
commit 3539764008
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 59
      pkg/bus/bus_test.go
  2. 4
      pkg/components/apikeygen/apikeygen_test.go
  3. 68
      pkg/components/imguploader/webdavuploader_test.go
  4. 48
      pkg/expr/classic/evaluator_test.go
  5. 51
      pkg/login/social/connectors/github_oauth_test.go
  6. 37
      pkg/middleware/cookies/cookies_test.go
  7. 60
      pkg/services/anonymous/anonimpl/impl_test.go
  8. 103
      pkg/services/cloudmigration/objectstorage/s3_test.go
  9. 115
      pkg/services/serviceaccounts/secretscan/service_test.go

@ -2,6 +2,7 @@ package bus
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
@ -91,3 +92,61 @@ func TestEventCtxPublish(t *testing.T) {
require.True(t, invoked)
}
func TestEventListenerError(t *testing.T) {
bus := ProvideBus(tracing.InitializeTracerForTest())
mockErr := errors.New("error")
invocations := 0
// Will be called in order of declaration.
bus.AddEventListener(func(ctx context.Context, query *testQuery) error {
invocations++
return nil
})
bus.AddEventListener(func(ctx context.Context, query *testQuery) error {
invocations++
return mockErr
})
bus.AddEventListener(func(ctx context.Context, query *testQuery) {
invocations++
})
err := bus.Publish(context.Background(), &testQuery{})
require.ErrorIs(t, err, mockErr)
require.Equal(t, 2, invocations)
}
func TestEventListenerInvalidCallbackType(t *testing.T) {
bus := ProvideBus(tracing.InitializeTracerForTest())
invoked := false
bus.AddEventListener(func(ctx context.Context, query *testQuery) bool {
invoked = true
return invoked
})
err := bus.Publish(context.Background(), &testQuery{})
require.Error(t, err)
require.True(t, invoked)
}
func TestEventListenerInvalidCallback(t *testing.T) {
bus := ProvideBus(tracing.InitializeTracerForTest())
invoked := false
bus.AddEventListener(func(ctx context.Context, query *testQuery) {
invoked = true
})
require.Panics(t, func() {
err := bus.Publish(context.Background(), &testQuery{})
require.NoError(t, err) // unreachable
})
require.True(t, invoked)
}

@ -22,4 +22,8 @@ func TestApiKeyGen(t *testing.T) {
keyHashed, err := util.EncodePassword(keyInfo.Key, keyInfo.Name)
require.NoError(t, err)
assert.Equal(t, result.HashedKey, keyHashed)
valid, err := IsValid(keyInfo, keyHashed)
require.NoError(t, err)
require.True(t, valid)
}

@ -2,43 +2,83 @@ package imguploader
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/webdav"
)
func TestUploadToWebdav(t *testing.T) {
// Can be tested with this docker container: https://hub.docker.com/r/morrisjobke/webdav/
t.Parallel()
t.Run("[Integration test] for external_image_store.webdav", func(t *testing.T) {
t.Skip("Skip test [Integration test] for external_image_store.webdav")
webdavUploader, _ := NewWebdavImageUploader("http://localhost:8888/webdav/", "test", "test", "")
path, err := webdavUploader.Upload(context.Background(), "../../../public/img/logo_transparent_400x.png")
t.Parallel()
handler := &webdav.Handler{
FileSystem: webdav.Dir(t.TempDir()),
LockSystem: webdav.NewMemLS(),
Logger: func(r *http.Request, err error) {
require.Equal(t, http.MethodPut, r.Method)
require.NoError(t, err)
},
}
server := httptest.NewServer(handler)
t.Cleanup(server.Close)
webdavUploader, err := NewWebdavImageUploader(server.URL, "test", "test", "")
require.NoError(t, err)
require.NotNil(t, webdavUploader)
path, err := webdavUploader.Upload(context.Background(), "../../../public/img/logo_transparent_400x.png")
require.NoError(t, err)
require.True(t, strings.HasPrefix(path, "http://localhost:8888/webdav/"))
require.True(t, strings.HasPrefix(path, server.URL))
})
t.Run("[Integration test] for external_image_store.webdav with public url", func(t *testing.T) {
t.Skip("Skip test [Integration test] for external_image_store.webdav with public url")
webdavUploader, _ := NewWebdavImageUploader("http://localhost:8888/webdav/", "test", "test", "http://publicurl:8888/webdav")
path, err := webdavUploader.Upload(context.Background(), "../../../public/img/logo_transparent_400x.png")
t.Parallel()
handler := &webdav.Handler{
FileSystem: webdav.Dir(t.TempDir()),
LockSystem: webdav.NewMemLS(),
Logger: func(r *http.Request, err error) {
require.Equal(t, http.MethodPut, r.Method)
require.NoError(t, err)
},
}
server := httptest.NewServer(handler)
t.Cleanup(server.Close)
webdavUploader, err := NewWebdavImageUploader(server.URL, "test", "test", "http://publicurl:8888/webdav")
require.NoError(t, err)
require.True(t, strings.HasPrefix(path, "http://publicurl:8888/webdav/"))
require.NotNil(t, webdavUploader)
path, err := webdavUploader.Upload(context.Background(), "../../../public/img/logo_transparent_400x.png")
require.NoError(t, err)
require.True(t, strings.HasPrefix(path, "http://publicurl:8888/webdav/"))
})
}
func TestPublicURL(t *testing.T) {
t.Parallel()
t.Run("Given a public URL with parameters, and no template", func(t *testing.T) {
webdavUploader, _ := NewWebdavImageUploader("http://localhost:8888/webdav/", "test", "test", "http://cloudycloud.me/s/DOIFDOMV/download?files=")
assert.Equal(t, "http://cloudycloud.me/s/DOIFDOMV/download/fileyfile.png?files=", webdavUploader.PublicURL("fileyfile.png"))
t.Parallel()
webdavUploader, err := NewWebdavImageUploader("http://localhost:8888/webdav/", "test", "test", "http://cloudycloud.me/s/DOIFDOMV/download?files=")
require.NoError(t, err)
require.Equal(t, "http://cloudycloud.me/s/DOIFDOMV/download/fileyfile.png?files=", webdavUploader.PublicURL("fileyfile.png"))
})
t.Run("Given a public URL with parameters, and a template", func(t *testing.T) {
webdavUploader, _ := NewWebdavImageUploader("http://localhost:8888/webdav/", "test", "test", "http://cloudycloud.me/s/DOIFDOMV/download?files={{file}}")
assert.Equal(t, "http://cloudycloud.me/s/DOIFDOMV/download?files=fileyfile.png", webdavUploader.PublicURL("fileyfile.png"))
t.Parallel()
webdavUploader, err := NewWebdavImageUploader("http://localhost:8888/webdav/", "test", "test", "http://cloudycloud.me/s/DOIFDOMV/download?files={{file}}")
require.NoError(t, err)
require.Equal(t, "http://cloudycloud.me/s/DOIFDOMV/download?files=fileyfile.png", webdavUploader.PublicURL("fileyfile.png"))
})
}

@ -148,6 +148,54 @@ func TestRangedEvaluator(t *testing.T) {
inputNumber: newNumber(util.Pointer(50.0)),
expected: false,
},
{
name: "value 100 is outside range 1, 100: false",
evaluator: &rangedEvaluator{"outside_range", 1, 100},
inputNumber: newNumber(util.Pointer(100.)),
expected: false,
},
{
name: "value 1 is outside range 1, 100: false",
evaluator: &rangedEvaluator{"outside_range", 1, 100},
inputNumber: newNumber(util.Pointer(1.)),
expected: false,
},
{
name: "value 100 is within range included 1, 100: true",
evaluator: &rangedEvaluator{"within_range_included", 1, 100},
inputNumber: newNumber(util.Pointer(100.)),
expected: true,
},
{
name: "value 1 is within range included 1, 100: true",
evaluator: &rangedEvaluator{"within_range_included", 1, 100},
inputNumber: newNumber(util.Pointer(1.)),
expected: true,
},
{
name: "value 100 is outside range included 1, 100: true",
evaluator: &rangedEvaluator{"outside_range_included", 1, 100},
inputNumber: newNumber(util.Pointer(100.)),
expected: true,
},
{
name: "value 1 is outside range included 1, 100: true",
evaluator: &rangedEvaluator{"outside_range_included", 1, 100},
inputNumber: newNumber(util.Pointer(1.)),
expected: true,
},
{
name: "unknown evaluator type returns false",
evaluator: &rangedEvaluator{"", 1, 100},
inputNumber: newNumber(util.Pointer(1.)),
expected: false,
},
{
name: "nil number conversion returns false",
evaluator: &rangedEvaluator{"", 1, 100},
inputNumber: newNumber(nil),
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

@ -2,6 +2,7 @@ package connectors
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
@ -86,7 +87,12 @@ const testGHUserTeamsJSON = `[
}
]`
const testGHUserJSON = `{
var (
testGHUserJSON = fmt.Sprintf(testGHUserJSONTemplate, "octocat@github.com")
testGHUserEmptyEmailJSON = fmt.Sprintf(testGHUserJSONTemplate, "")
)
const testGHUserJSONTemplate = `{
"login": "octocat",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
@ -109,7 +115,7 @@ const testGHUserJSON = `{
"company": "GitHub",
"blog": "https://github.com/blog",
"location": "San Francisco",
"email": "octocat@github.com",
"email": "%s",
"hireable": false,
"bio": "There once was...",
"twitter_username": "monatheoctocat",
@ -133,6 +139,16 @@ const testGHUserJSON = `{
}
}`
const testGHUserEmailJSON = `[{
"email": "octocat@github.com",
"primary": true,
"verified": true
}]`
const testGHOrgsJSON = `[{
"login": "github"
}]`
func TestSocialGitHub_UserInfo(t *testing.T) {
var boolPointer *bool
tests := []struct {
@ -310,20 +326,45 @@ func TestSocialGitHub_UserInfo(t *testing.T) {
userTeamsRawJSON: testGHUserTeamsJSON,
wantErr: true,
},
{
name: "should fetch email and allowed orgs",
userRawJSON: testGHUserEmptyEmailJSON,
userTeamsRawJSON: testGHUserTeamsJSON,
oAuthExtraInfo: map[string]string{
"allowed_organizations": "github",
},
want: &social.BasicUserInfo{
Id: "1",
Name: "monalisa octocat",
Email: "octocat@github.com",
Login: "octocat",
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusOK)
reqURL := request.URL.String()
// return JSON if matches user endpoint
if strings.HasSuffix(request.URL.String(), "/user") {
if strings.HasSuffix(reqURL, "/user") {
writer.Header().Set("Content-Type", "application/json")
_, err := writer.Write([]byte(tt.userRawJSON))
require.NoError(t, err)
} else if strings.HasSuffix(request.URL.String(), "/user/teams?per_page=100") {
} else if strings.HasSuffix(reqURL, "/user/teams?per_page=100") {
writer.Header().Set("Content-Type", "application/json")
_, err := writer.Write([]byte(tt.userTeamsRawJSON))
require.NoError(t, err)
} else if strings.HasSuffix(reqURL, "/emails") { // only called if email is empty
writer.Header().Set("Content-Type", "application/json")
_, err := writer.Write([]byte(testGHUserEmailJSON))
require.NoError(t, err)
} else if strings.HasSuffix(reqURL, "/orgs?per_page=100") {
writer.Header().Set("Content-Type", "application/json")
_, err := writer.Write([]byte(testGHOrgsJSON))
require.NoError(t, err)
} else {
writer.WriteHeader(http.StatusNotFound)
}

@ -0,0 +1,37 @@
package cookies
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
)
func TestCookieOptions(t *testing.T) {
rr := httptest.NewRecorder()
expectedName := "cookie-name"
expectedValue := "cookie-value"
WriteCookie(rr, expectedName, expectedValue, 100, nil)
cookie, err := http.ParseSetCookie(rr.Header().Get("Set-Cookie"))
require.NoError(t, err)
require.NotNil(t, cookie)
require.Equal(t, expectedName, cookie.Name)
require.Equal(t, expectedValue, cookie.Value)
require.GreaterOrEqual(t, cookie.MaxAge, 0)
// Does not override but appends to the `Set-Cookie` header.
DeleteCookie(rr, expectedName, nil)
cookieHeader := rr.Header().Values("Set-Cookie")
require.Len(t, cookieHeader, 2)
cookie, err = http.ParseSetCookie(cookieHeader[1])
require.NoError(t, err)
require.NotNil(t, cookie)
require.NoError(t, cookie.Valid())
}

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/services/anonymous"
"github.com/grafana/grafana/pkg/services/anonymous/anonimpl/anonstore"
"github.com/grafana/grafana/pkg/services/anonymous/validator"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/services/org/orgtest"
"github.com/grafana/grafana/pkg/setting"
@ -41,6 +42,7 @@ func TestIntegrationDeviceService_tag(t *testing.T) {
expectedAnonUICount int64
expectedKey string
expectedDevice *anonstore.Device
disableService bool
}{
{
name: "no requests",
@ -118,20 +120,49 @@ func TestIntegrationDeviceService_tag(t *testing.T) {
},
expectedAnonUICount: 2,
},
{
name: "when the service is disabled, read operations return empty",
req: []tagReq{
{
httpReq: &http.Request{
Header: http.Header{
"User-Agent": []string{"test"},
"X-Forwarded-For": []string{"10.30.30.1"},
http.CanonicalHeaderKey(deviceIDHeader): []string{"32mdo31deeqwes"},
},
},
kind: anonymous.AnonDeviceUI,
},
},
disableService: true,
expectedAnonUICount: 0,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
store := db.InitTestDB(t)
anonService := ProvideAnonymousDeviceService(&usagestats.UsageStatsMock{},
&authntest.FakeService{}, store, setting.NewCfg(), orgtest.NewOrgServiceFake(), nil, actest.FakeAccessControl{}, &routing.RouteRegisterImpl{}, validator.FakeAnonUserLimitValidator{})
cfg := setting.NewCfg()
cfg.Anonymous.Enabled = !tc.disableService
anonService := ProvideAnonymousDeviceService(
&usagestats.UsageStatsMock{}, &authntest.FakeService{}, store, cfg, orgtest.NewOrgServiceFake(),
nil, actest.FakeAccessControl{}, &routing.RouteRegisterImpl{}, validator.FakeAnonUserLimitValidator{},
)
for _, req := range tc.req {
err := anonService.TagDevice(context.Background(), req.httpReq, req.kind)
err := anonService.TagDevice(ctx, req.httpReq, req.kind)
require.NoError(t, err)
t.Cleanup(func() {
anonService.untagDevice(ctx, nil, &authn.Request{HTTPRequest: req.httpReq}, nil)
})
}
devices, err := anonService.anonStore.ListDevices(context.Background(), nil, nil)
devices, err := anonService.ListDevices(ctx, nil, nil)
require.NoError(t, err)
require.Len(t, devices, int(tc.expectedAnonUICount))
if tc.expectedDevice != nil {
@ -147,10 +178,29 @@ func TestIntegrationDeviceService_tag(t *testing.T) {
assert.Equal(t, tc.expectedDevice, devices[0])
}
to := time.Now()
from := to.AddDate(0, 0, -1)
devicesCount, err := anonService.CountDevices(ctx, from, to)
require.NoError(t, err)
require.Equal(t, tc.expectedAnonUICount, devicesCount)
devicesFound, err := anonService.SearchDevices(ctx, &anonstore.SearchDeviceQuery{
From: from,
To: to,
})
require.NoError(t, err)
if tc.expectedAnonUICount > 0 {
require.NotNil(t, devicesFound)
require.Equal(t, tc.expectedAnonUICount, devicesFound.TotalCount)
}
stats, err := anonService.usageStatFn(context.Background())
require.NoError(t, err)
assert.Equal(t, tc.expectedAnonUICount, stats["stats.anonymous.device.ui.count"].(int64), stats)
if !tc.disableService {
assert.Equal(t, tc.expectedAnonUICount, stats["stats.anonymous.device.ui.count"].(int64), stats)
}
})
}
}

@ -0,0 +1,103 @@
package objectstorage
import (
"bytes"
"context"
"io"
"math"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/stretchr/testify/require"
)
func TestPresignedURLUpload(t *testing.T) {
t.Parallel()
t.Run("successfully send data to the server", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
key := "snapshot/uuid/key"
data := "sending-some-data"
reader := bytes.NewBufferString(data)
qs, err := url.ParseQuery("one=a&two=b")
require.NoError(t, err)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
contentType := r.Header.Get("Content-Type")
_, boundary, found := strings.Cut(contentType, "boundary=")
require.True(t, found)
mpr := multipart.NewReader(r.Body, boundary)
form, err := mpr.ReadForm(math.MaxInt64)
require.NoError(t, err)
require.NotNil(t, form)
require.NotNil(t, form.Value)
require.Equal(t, key, form.Value["key"][0])
require.Equal(t, qs.Get("one"), form.Value["one"][0])
require.Equal(t, qs.Get("two"), form.Value["two"][0])
require.Len(t, form.File, 1)
require.Len(t, form.File["file"], 1)
fileHeader := form.File["file"][0]
require.Equal(t, "file", fileHeader.Filename)
file, err := fileHeader.Open()
require.NoError(t, err)
contents, err := io.ReadAll(file)
require.NoError(t, err)
require.EqualValues(t, data, string(contents))
require.NoError(t, file.Close())
}))
t.Cleanup(server.Close)
s3 := NewS3(http.DefaultClient, tracing.NewNoopTracerService())
presignedURL, err := url.Parse(server.URL + "?" + qs.Encode())
require.NoError(t, err)
err = s3.PresignedURLUpload(ctx, presignedURL.String(), key, reader)
require.NoError(t, err)
})
t.Run("when the request to the server returns an error, it is propagated", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
key := "snapshot/uuid/key"
data := "sending-some-data"
reader := bytes.NewBufferString(data)
body := "test error"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"message": "` + body + `}`))
}))
t.Cleanup(server.Close)
s3 := NewS3(http.DefaultClient, tracing.NewNoopTracerService())
presignedURL, err := url.Parse(server.URL)
require.NoError(t, err)
err = s3.PresignedURLUpload(ctx, presignedURL.String(), key, reader)
require.Error(t, err)
require.Contains(t, err.Error(), body)
})
}

@ -2,13 +2,20 @@ package secretscan
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/setting"
)
func TestService_CheckTokens(t *testing.T) {
@ -170,3 +177,111 @@ func TestService_CheckTokens(t *testing.T) {
})
}
}
func TestService(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
// Fake Secret Scanner + Webhook.
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.RequestURI, "/tokens") {
_, err := io.Copy(io.Discard, r.Body)
require.NoError(t, err)
defer func() {
_ = r.Body.Close()
}()
_, _ = w.Write([]byte(`[
{"type": "token_type", "hash": "test-hash-1", "url": "http://example.com", "reported_at": "2006-01-20T01:02:03Z" }
]`))
}
if strings.Contains(r.RequestURI, "/oncall") {
var webhookReq struct {
State string `json:"state"`
Message string `json:"message"`
}
err := json.NewDecoder(r.Body).Decode(&webhookReq)
require.NoError(t, err)
defer func() {
_ = r.Body.Close()
}()
require.Equal(t, "alerting", webhookReq.State)
require.Contains(t, webhookReq.Message, "test-1")
}
}))
t.Cleanup(server.Close)
unixZero := time.Unix(0, 0).Unix()
revoked := true
tokenRetriever := &MockTokenRetriever{keys: []apikey.APIKey{
// Valid
{
ID: 1,
OrgID: 1,
Name: "test-1",
Key: "test-hash-1",
Role: "Viewer",
Expires: nil,
ServiceAccountId: new(int64),
IsRevoked: new(bool),
},
// Expired
{
ID: 2,
OrgID: 1,
Name: "test-2",
Key: "test-hash-2",
Role: "Viewer",
Expires: &unixZero,
ServiceAccountId: new(int64),
IsRevoked: new(bool),
},
// Revoked
{
ID: 3,
OrgID: 1,
Name: "test-3",
Key: "test-hash-3",
Role: "Viewer",
Expires: nil,
ServiceAccountId: new(int64),
IsRevoked: &revoked,
},
// Revoked + Expired
{
ID: 4,
OrgID: 1,
Name: "test-4",
Key: "test-hash-4",
Role: "Viewer",
Expires: &unixZero,
ServiceAccountId: new(int64),
IsRevoked: &revoked,
},
}}
cfg := setting.NewCfg()
section := cfg.Raw.Section("secretscan")
baseURL := section.Key("base_url")
baseURL.SetValue(server.URL)
oncallURL := section.Key("oncall_url")
oncallURL.SetValue(server.URL + "/oncall")
revoke := section.Key("revoke")
revoke.SetValue("true")
service, err := NewService(tokenRetriever, cfg)
require.NoError(t, err)
require.NotNil(t, service)
err = service.CheckTokens(ctx)
require.NoError(t, err)
}

Loading…
Cancel
Save