Files
Gokapi/internal/test/TestHelper.go
Marc Bulling d88f649209 Add support for multiple different users, rewrote API, breaking API changes, UI changes
* Require 1.9.6 for upgrade, add function to get userID from request

* Automatically add user when successfully authenticated with headers / oauth, disallow modifing own user permissions

* Dont show user/pw page when using header authentication

* Only display redacted versions of API keys #228, fixed deployment password

* Added animation for deleting API key

* Only create salt once

* Disable elements on upload UI if insufficient permissions

* BREAKING: User field must be email for OAUTH2, added warning in setup when changing database

* BREAKING: Added option to restrict to only registered users

* Fixed crash due to concurrent map iteration

* Replace /uploadComplete with API call, BREAKING API is now in headers

* BREAKING: require true|false instead of only checking for true

* BREAKING API: Renamed apiKeyToModify parameter to targetKey
2025-02-04 09:22:55 +01:00

457 lines
11 KiB
Go

//go:build test
package test
import (
"bytes"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
)
type MockT interface {
Errorf(format string, args ...interface{})
Helper()
}
func IsEqualByteSlice(t MockT, got, want []byte) {
t.Helper()
if !bytes.Equal(got, want) {
t.Errorf("Assertion failed, GOT: %s, WANT: %s.", string(got), string(want))
}
}
// IsEqualString fails test if got and want are not identical
func IsEqualString(t MockT, got, want string) {
t.Helper()
if got != want {
t.Errorf("Assertion failed, GOT: %s, WANT: %s.", got, want)
}
}
// ResponseBodyContains fails test if http response does contain string
func ResponseBodyContains(t MockT, got *httptest.ResponseRecorder, want string) {
t.Helper()
result, err := io.ReadAll(got.Result().Body)
IsNil(t, err)
if !strings.Contains(string(result), want) {
t.Errorf("Assertion failed, got: %v \n want: %s.\n\n", got, want)
}
}
// IsNotEqualString fails test if got and want are not identical
func IsNotEqualString(t MockT, got, want string) {
t.Helper()
if got == want {
t.Errorf("Assertion failed, got: %s, want: not %s.", got, want)
}
}
// IsEqualBool fails test if got and want are not identical
func IsEqualBool(t MockT, got, want bool) {
t.Helper()
if got != want {
t.Errorf("Assertion failed, got: %t, want: %t.", got, want)
}
}
// IsEqualInt fails test if got and want are not identical
func IsEqualInt(t MockT, got, want int) {
t.Helper()
if got != want {
t.Errorf("Assertion failed, got: %d, want: %d.", got, want)
}
}
// IsEqualInt64 fails test if got and want are not identical
func IsEqualInt64(t MockT, got, want int64) {
t.Helper()
if got != want {
t.Errorf("Assertion failed, got: %d, want: %d.", got, want)
}
}
// IsNotEmpty fails test if string is empty
func IsNotEmpty(t MockT, s string) {
t.Helper()
if s == "" {
t.Errorf("Assertion failed, got: %s, want: empty.", s)
}
}
// IsEmpty fails test if string is not empty
func IsEmpty(t MockT, s string) {
t.Helper()
if s != "" {
t.Errorf("Assertion failed, got: %s, want: empty.", s)
}
}
// IsNil fails test if object is not nil. If object is an error, it will display the error message
func IsNil(t MockT, got any) {
t.Helper()
if got == nil {
return
}
err, ok := got.(error)
if !ok {
t.Errorf("Assertion failed, got: not nil, want: nil.")
} else {
t.Errorf("Assertion failed, got: %s, want: nil.", err.Error())
}
}
// IsNilWithMessage fails test if error not nil and name of test
func IsNilWithMessage(t MockT, got error, testName string) {
t.Helper()
if got != nil {
t.Errorf("%s: Assertion failed, got: %s, want: nil.", testName, got.(error).Error())
}
}
// FileExists fails test a file does not exist
func FileExists(t MockT, name string) {
t.Helper()
if !fileExists(name) {
t.Errorf("Assertion failed, file does not exist: %s, want: Exists.", name)
}
}
// FolderExists fails test a folder does not exist
func FolderExists(t MockT, name string) {
t.Helper()
_, err := os.Stat(name)
if err == nil {
return
}
if !os.IsNotExist(err) {
t.Errorf("Assertion failed, folder does not exist: %s, want: Exists.", name)
} else {
t.Errorf("Assertion failed, could not check if folder exist: %s.", name)
}
}
// FileDoesNotExist fails test a file exists
func FileDoesNotExist(t MockT, name string) {
t.Helper()
if fileExists(name) {
t.Errorf("Assertion failed, file exist: %s, want: Does not exist", name)
}
}
// Copy of helper.FileExists, which cannot be used due to import cycle
func fileExists(name string) bool {
info, err := os.Stat(name)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
// IsNotNil fails test if input is nil
func IsNotNil(t MockT, got any) {
t.Helper()
if got == nil {
t.Errorf("Assertion failed, got: nil, want: not nil.")
}
}
// IsEqual fails test if got and want are not identical
func IsEqual(t MockT, got, expected any) {
t.Helper()
if !reflect.DeepEqual(got, expected) {
t.Errorf("Assertion failed, not equal: %v and %v", got, expected)
}
}
// IsNotNilWithMessage fails test if error is nil and displays name of test
func IsNotNilWithMessage(t MockT, got error, name string) {
t.Helper()
if got == nil {
t.Errorf("%s: Assertion failed, got: nil, want: not nil.", name)
}
}
// ExitCode returns a function to replace os.Exit()
func ExitCode(t MockT, want int) func(code int) {
t.Helper()
return func(code int) {
IsEqualInt(t, code, want)
}
}
// StartMockInputStdin simulates a user input on stdin. Call StopMockInputStdin afterwards!
func StartMockInputStdin(input string) *os.File {
r, w, err := os.Pipe()
if err != nil {
panic(err)
}
_, err = w.Write([]byte(input))
if err != nil {
panic(err)
}
w.Close()
stdin := os.Stdin
os.Stdin = r
return stdin
}
func CompletesWithinTime(t MockT, function func(), d time.Duration) {
t.Helper()
c := make(chan bool, 1)
go func() {
function()
c <- true
}()
select {
case res := <-c:
IsEqualBool(t, res, true)
case <-time.After(d):
t.Errorf("Timeout of function")
}
}
// StopMockInputStdin needs to be called after StartMockInputStdin
func StopMockInputStdin(stdin *os.File) {
os.Stdin = stdin
}
// HttpPageResult tests if a http server is outputting the correct result
func HttpPageResult(t MockT, config HttpTestConfig) []*http.Cookie {
t.Helper()
config.init(t)
client := &http.Client{}
data := url.Values{}
for _, value := range config.PostValues {
data.Add(value.Key, value.Value)
}
req, err := http.NewRequest(config.Method, config.Url, strings.NewReader(data.Encode()))
IsNil(t, err)
for _, cookie := range config.Cookies {
req.Header.Set("Cookie", cookie.toString())
}
for _, header := range config.Headers {
req.Header.Set(header.Name, header.Value)
}
if len(config.PostValues) > 0 {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
}
resp, err := client.Do(req)
IsNil(t, err)
checkResponse(t, resp, config)
return resp.Cookies()
}
// HttpPageResultJson tests if a http server is outputting the correct result
func HttpPageResultJson(t MockT, config HttpTestConfig) []*http.Cookie {
t.Helper()
config.init(t)
client := &http.Client{}
req, err := http.NewRequest(config.Method, config.Url, config.Body)
IsNil(t, err)
for _, cookie := range config.Cookies {
req.Header.Set("Cookie", cookie.toString())
}
for _, header := range config.Headers {
req.Header.Set(header.Name, header.Value)
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
IsNil(t, err)
defer resp.Body.Close()
checkResponse(t, resp, config)
return resp.Cookies()
}
func checkResponse(t MockT, response *http.Response, config HttpTestConfig) {
t.Helper()
IsEqualBool(t, response != nil, true)
if response.StatusCode != config.ResultCode {
t.Errorf("Status Code - Got: %d Want: %d", response.StatusCode, config.ResultCode)
}
content, err := io.ReadAll(response.Body)
IsNil(t, err)
if config.IsHtml && !bytes.Contains(content, []byte("</html>")) {
t.Errorf(config.Url + ": Incorrect response, no HTML tag")
}
for _, requiredString := range config.RequiredContent {
if !bytes.Contains(content, []byte(requiredString)) {
t.Errorf(config.Url + ": Incorrect response. Got:\n" + string(content))
}
}
for _, excludedString := range config.ExcludedContent {
if bytes.Contains(content, []byte(excludedString)) {
t.Errorf(config.Url + ": Incorrect response. Got:\n" + string(content))
}
}
}
// HttpTestConfig is a struct for http test init
type HttpTestConfig struct {
Url string
RequiredContent []string
ExcludedContent []string
IsHtml bool
Method string
PostValues []PostBody
Cookies []Cookie
Headers []Header
UploadFileName string
UploadFieldName string
ResultCode int
Body io.Reader
}
func (c *HttpTestConfig) init(t MockT) {
t.Helper()
if c.Url == "" {
t.Errorf("No url passed!")
}
if c.Method == "" {
c.Method = "GET"
}
if c.ResultCode == 0 {
c.ResultCode = 200
}
}
// Cookie is a simple struct to pass cookie values for testing
type Cookie struct {
Name string
Value string
}
func (c *Cookie) toString() string {
return c.Name + "=" + c.Value
}
// Header is a simple struct to pass header values for testing
type Header struct {
Name string
Value string
}
// PostBody contains mock key/value post data
type PostBody struct {
Key string
Value string
}
// HttpPostUploadRequest sends a post request with a file upload
func HttpPostUploadRequest(t MockT, config HttpTestConfig) {
t.Helper()
config.init(t)
body, formcontent := FileToMultipartFormBody(t, config)
request, err := http.NewRequest("POST", config.Url, body)
IsNil(t, err)
for _, cookie := range config.Cookies {
request.Header.Set("Cookie", cookie.toString())
}
request.Header.Add("Content-Type", formcontent)
client := &http.Client{}
response, err := client.Do(request)
IsNil(t, err)
defer response.Body.Close()
checkResponse(t, response, config)
}
func FileToMultipartFormBody(t MockT, config HttpTestConfig) (*bytes.Buffer, string) {
file, err := os.Open(config.UploadFileName)
IsNil(t, err)
defer file.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
for _, postValue := range config.PostValues {
err = writer.WriteField(postValue.Key, postValue.Value)
IsNil(t, err)
}
part, err := writer.CreateFormFile(config.UploadFieldName, filepath.Base(file.Name()))
IsNil(t, err)
_, err = io.Copy(part, file)
IsNil(t, err)
defer writer.Close()
return body, writer.FormDataContentType()
}
// HttpPostRequest sends a post request
func HttpPostRequest(t MockT, config HttpTestConfig) []*http.Cookie {
t.Helper()
config.init(t)
data := url.Values{}
for _, dataField := range config.PostValues {
data.Add(dataField.Key, dataField.Value)
}
r, err := http.NewRequest("POST", config.Url, strings.NewReader(data.Encode()))
IsNil(t, err)
for _, cookie := range config.Cookies {
r.AddCookie(&http.Cookie{
Name: cookie.Name,
Value: cookie.Value,
Path: "/",
})
}
r.Header.Set("Content-type", "application/x-www-form-urlencoded")
for _, header := range config.Headers {
r.Header.Set(header.Name, header.Value)
}
client := &http.Client{}
response, err := client.Do(r)
IsNil(t, err)
defer response.Body.Close()
checkResponse(t, response, config)
return response.Cookies()
}
func GetRecorder(method, target string, cookies []Cookie, headers []Header, body io.Reader) (*httptest.ResponseRecorder, *http.Request) {
w := httptest.NewRecorder()
r := httptest.NewRequest(method, target, body)
if cookies != nil {
for _, cookie := range cookies {
r.AddCookie(&http.Cookie{
Name: cookie.Name,
Value: cookie.Value,
Path: "/",
})
}
}
if headers != nil {
for _, header := range headers {
r.Header.Set(header.Name, header.Value)
}
}
return w, r
}
func ExpectPanic(t MockT) {
r := recover()
t.Helper()
if r == nil {
t.Errorf("The code did not panic")
}
}