mirror of
https://github.com/Forceu/Gokapi.git
synced 2026-01-06 00:49:33 -06:00
* 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
457 lines
11 KiB
Go
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")
|
|
}
|
|
}
|