initial public commit
@@ -0,0 +1,121 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
)
|
||||
|
||||
// ApiScenario defines a single api request test case/scenario.
|
||||
type ApiScenario struct {
|
||||
Name string
|
||||
Method string
|
||||
Url string
|
||||
Body io.Reader
|
||||
RequestHeaders map[string]string
|
||||
// expectations
|
||||
ExpectedStatus int
|
||||
ExpectedContent []string
|
||||
ExpectedEvents map[string]int
|
||||
// test events
|
||||
BeforeFunc func(t *testing.T, app *TestApp, e *echo.Echo)
|
||||
AfterFunc func(t *testing.T, app *TestApp, e *echo.Echo)
|
||||
}
|
||||
|
||||
// Test executes the test case/scenario.
|
||||
func (scenario *ApiScenario) Test(t *testing.T) {
|
||||
testApp, _ := NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
e, err := apis.InitApi(testApp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if scenario.BeforeFunc != nil {
|
||||
scenario.BeforeFunc(t, testApp, e)
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(scenario.Method, scenario.Url, scenario.Body)
|
||||
|
||||
// add middeware to timeout long running requests (eg. keep-alive routes)
|
||||
e.Pre(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx, cancelFunc := context.WithTimeout(c.Request().Context(), 100*time.Millisecond)
|
||||
defer cancelFunc()
|
||||
c.SetRequest(c.Request().Clone(ctx))
|
||||
return next(c)
|
||||
}
|
||||
})
|
||||
|
||||
// set default header
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
|
||||
// set scenario headers
|
||||
for k, v := range scenario.RequestHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
// execute request
|
||||
e.ServeHTTP(recorder, req)
|
||||
|
||||
res := recorder.Result()
|
||||
|
||||
var prefix = scenario.Name
|
||||
if prefix == "" {
|
||||
prefix = fmt.Sprintf("%s:%s", scenario.Method, scenario.Url)
|
||||
}
|
||||
|
||||
if res.StatusCode != scenario.ExpectedStatus {
|
||||
t.Errorf("[%s] Expected status code %d, got %d", prefix, scenario.ExpectedStatus, res.StatusCode)
|
||||
}
|
||||
|
||||
if len(scenario.ExpectedContent) == 0 {
|
||||
if len(recorder.Body.Bytes()) != 0 {
|
||||
t.Errorf("[%s] Expected empty body, got %v", prefix, recorder.Body.String())
|
||||
}
|
||||
} else {
|
||||
// normalize json response format
|
||||
buffer := new(bytes.Buffer)
|
||||
err := json.Compact(buffer, recorder.Body.Bytes())
|
||||
var normalizedBody string
|
||||
if err != nil {
|
||||
// not a json...
|
||||
normalizedBody = recorder.Body.String()
|
||||
} else {
|
||||
normalizedBody = buffer.String()
|
||||
}
|
||||
|
||||
for _, item := range scenario.ExpectedContent {
|
||||
if !strings.Contains(normalizedBody, item) {
|
||||
t.Errorf("[%s] Cannot find %v in response body %v", prefix, item, normalizedBody)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(testApp.EventCalls) > len(scenario.ExpectedEvents) {
|
||||
t.Errorf("[%s] Expected events %v, got %v", prefix, scenario.ExpectedEvents, testApp.EventCalls)
|
||||
}
|
||||
|
||||
for event, expectedCalls := range scenario.ExpectedEvents {
|
||||
actualCalls, _ := testApp.EventCalls[event]
|
||||
if actualCalls != expectedCalls {
|
||||
t.Errorf("[%s] Expected event %s to be called %d, got %d", prefix, event, expectedCalls, actualCalls)
|
||||
}
|
||||
}
|
||||
|
||||
if scenario.AfterFunc != nil {
|
||||
scenario.AfterFunc(t, testApp, e)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
// Pacakge tests provides common helpers and mocks used in PocketBase application tests.
|
||||
package tests
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
)
|
||||
|
||||
// TestApp is a wrapper app instance used for testing.
|
||||
type TestApp struct {
|
||||
*core.BaseApp
|
||||
|
||||
// EventCalls defines a map to inspect which app events
|
||||
// (and how many times) were triggered.
|
||||
EventCalls map[string]int
|
||||
TestMailer *TestMailer
|
||||
}
|
||||
|
||||
// Cleanup resets the test application state and removes the test
|
||||
// app's dataDir from the filesystem.
|
||||
//
|
||||
// After this call, the app instance shouldn't be used anymore.
|
||||
func (t *TestApp) Cleanup() {
|
||||
t.ResetEventCalls()
|
||||
t.ResetBootstrapState()
|
||||
|
||||
if t.DataDir() != "" {
|
||||
os.RemoveAll(t.DataDir())
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TestApp) NewMailClient() mailer.Mailer {
|
||||
t.TestMailer.Reset()
|
||||
return t.TestMailer
|
||||
}
|
||||
|
||||
// ResetEventCalls resets the EventCalls counter.
|
||||
func (t *TestApp) ResetEventCalls() {
|
||||
t.EventCalls = make(map[string]int)
|
||||
}
|
||||
|
||||
// NewTestApp creates and initializes a full application instance for testing.
|
||||
//
|
||||
// It is the caller's responsibility to call `app.Cleanup()`
|
||||
// when the app is no longer needed.
|
||||
func NewTestApp() (*TestApp, error) {
|
||||
tempDir, err := NewTempDataDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
app := core.NewBaseApp(tempDir, "pb_test_env", false)
|
||||
|
||||
// load data dir and db connections
|
||||
if err := app.Bootstrap(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// force disable request logs because the logs db call execute in a separate
|
||||
// go routine and it is possible to panic due to earlier api test completion.
|
||||
app.Settings().Logs.MaxDays = 0
|
||||
|
||||
t := &TestApp{
|
||||
BaseApp: app,
|
||||
EventCalls: make(map[string]int),
|
||||
TestMailer: &TestMailer{},
|
||||
}
|
||||
|
||||
// no need to count since this is executed always
|
||||
// t.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
// t.EventCalls["OnBeforeServe"]++
|
||||
// return nil
|
||||
// })
|
||||
|
||||
t.OnModelBeforeCreate().Add(func(e *core.ModelEvent) error {
|
||||
t.EventCalls["OnModelBeforeCreate"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnModelAfterCreate().Add(func(e *core.ModelEvent) error {
|
||||
t.EventCalls["OnModelAfterCreate"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnModelBeforeUpdate().Add(func(e *core.ModelEvent) error {
|
||||
t.EventCalls["OnModelBeforeUpdate"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnModelAfterUpdate().Add(func(e *core.ModelEvent) error {
|
||||
t.EventCalls["OnModelAfterUpdate"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnModelBeforeDelete().Add(func(e *core.ModelEvent) error {
|
||||
t.EventCalls["OnModelBeforeDelete"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnModelAfterDelete().Add(func(e *core.ModelEvent) error {
|
||||
t.EventCalls["OnModelAfterDelete"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRecordsListRequest().Add(func(e *core.RecordsListEvent) error {
|
||||
t.EventCalls["OnRecordsListRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRecordViewRequest().Add(func(e *core.RecordViewEvent) error {
|
||||
t.EventCalls["OnRecordViewRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRecordBeforeCreateRequest().Add(func(e *core.RecordCreateEvent) error {
|
||||
t.EventCalls["OnRecordBeforeCreateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error {
|
||||
t.EventCalls["OnRecordAfterCreateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRecordBeforeUpdateRequest().Add(func(e *core.RecordUpdateEvent) error {
|
||||
t.EventCalls["OnRecordBeforeUpdateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRecordAfterUpdateRequest().Add(func(e *core.RecordUpdateEvent) error {
|
||||
t.EventCalls["OnRecordAfterUpdateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRecordBeforeDeleteRequest().Add(func(e *core.RecordDeleteEvent) error {
|
||||
t.EventCalls["OnRecordBeforeDeleteRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRecordAfterDeleteRequest().Add(func(e *core.RecordDeleteEvent) error {
|
||||
t.EventCalls["OnRecordAfterDeleteRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUsersListRequest().Add(func(e *core.UsersListEvent) error {
|
||||
t.EventCalls["OnUsersListRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserViewRequest().Add(func(e *core.UserViewEvent) error {
|
||||
t.EventCalls["OnUserViewRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserBeforeCreateRequest().Add(func(e *core.UserCreateEvent) error {
|
||||
t.EventCalls["OnUserBeforeCreateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserAfterCreateRequest().Add(func(e *core.UserCreateEvent) error {
|
||||
t.EventCalls["OnUserAfterCreateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserBeforeUpdateRequest().Add(func(e *core.UserUpdateEvent) error {
|
||||
t.EventCalls["OnUserBeforeUpdateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserAfterUpdateRequest().Add(func(e *core.UserUpdateEvent) error {
|
||||
t.EventCalls["OnUserAfterUpdateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserBeforeDeleteRequest().Add(func(e *core.UserDeleteEvent) error {
|
||||
t.EventCalls["OnUserBeforeDeleteRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserAfterDeleteRequest().Add(func(e *core.UserDeleteEvent) error {
|
||||
t.EventCalls["OnUserAfterDeleteRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserBeforeOauth2Register().Add(func(e *core.UserOauth2RegisterEvent) error {
|
||||
t.EventCalls["OnUserBeforeOauth2Register"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserAfterOauth2Register().Add(func(e *core.UserOauth2RegisterEvent) error {
|
||||
t.EventCalls["OnUserAfterOauth2Register"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnUserAuthRequest().Add(func(e *core.UserAuthEvent) error {
|
||||
t.EventCalls["OnUserAuthRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnMailerBeforeAdminResetPasswordSend().Add(func(e *core.MailerAdminEvent) error {
|
||||
t.EventCalls["OnMailerBeforeAdminResetPasswordSend"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnMailerAfterAdminResetPasswordSend().Add(func(e *core.MailerAdminEvent) error {
|
||||
t.EventCalls["OnMailerAfterAdminResetPasswordSend"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnMailerBeforeUserResetPasswordSend().Add(func(e *core.MailerUserEvent) error {
|
||||
t.EventCalls["OnMailerBeforeUserResetPasswordSend"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnMailerAfterUserResetPasswordSend().Add(func(e *core.MailerUserEvent) error {
|
||||
t.EventCalls["OnMailerAfterUserResetPasswordSend"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnMailerBeforeUserVerificationSend().Add(func(e *core.MailerUserEvent) error {
|
||||
t.EventCalls["OnMailerBeforeUserVerificationSend"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnMailerAfterUserVerificationSend().Add(func(e *core.MailerUserEvent) error {
|
||||
t.EventCalls["OnMailerAfterUserVerificationSend"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnMailerBeforeUserChangeEmailSend().Add(func(e *core.MailerUserEvent) error {
|
||||
t.EventCalls["OnMailerBeforeUserChangeEmailSend"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnMailerAfterUserChangeEmailSend().Add(func(e *core.MailerUserEvent) error {
|
||||
t.EventCalls["OnMailerAfterUserChangeEmailSend"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRealtimeConnectRequest().Add(func(e *core.RealtimeConnectEvent) error {
|
||||
t.EventCalls["OnRealtimeConnectRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRealtimeBeforeSubscribeRequest().Add(func(e *core.RealtimeSubscribeEvent) error {
|
||||
t.EventCalls["OnRealtimeBeforeSubscribeRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnRealtimeAfterSubscribeRequest().Add(func(e *core.RealtimeSubscribeEvent) error {
|
||||
t.EventCalls["OnRealtimeAfterSubscribeRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnSettingsListRequest().Add(func(e *core.SettingsListEvent) error {
|
||||
t.EventCalls["OnSettingsListRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnSettingsBeforeUpdateRequest().Add(func(e *core.SettingsUpdateEvent) error {
|
||||
t.EventCalls["OnSettingsBeforeUpdateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnSettingsAfterUpdateRequest().Add(func(e *core.SettingsUpdateEvent) error {
|
||||
t.EventCalls["OnSettingsAfterUpdateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnCollectionsListRequest().Add(func(e *core.CollectionsListEvent) error {
|
||||
t.EventCalls["OnCollectionsListRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnCollectionViewRequest().Add(func(e *core.CollectionViewEvent) error {
|
||||
t.EventCalls["OnCollectionViewRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnCollectionBeforeCreateRequest().Add(func(e *core.CollectionCreateEvent) error {
|
||||
t.EventCalls["OnCollectionBeforeCreateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnCollectionAfterCreateRequest().Add(func(e *core.CollectionCreateEvent) error {
|
||||
t.EventCalls["OnCollectionAfterCreateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnCollectionBeforeUpdateRequest().Add(func(e *core.CollectionUpdateEvent) error {
|
||||
t.EventCalls["OnCollectionBeforeUpdateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnCollectionAfterUpdateRequest().Add(func(e *core.CollectionUpdateEvent) error {
|
||||
t.EventCalls["OnCollectionAfterUpdateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnCollectionBeforeDeleteRequest().Add(func(e *core.CollectionDeleteEvent) error {
|
||||
t.EventCalls["OnCollectionBeforeDeleteRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnCollectionAfterDeleteRequest().Add(func(e *core.CollectionDeleteEvent) error {
|
||||
t.EventCalls["OnCollectionAfterDeleteRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnAdminsListRequest().Add(func(e *core.AdminsListEvent) error {
|
||||
t.EventCalls["OnAdminsListRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnAdminViewRequest().Add(func(e *core.AdminViewEvent) error {
|
||||
t.EventCalls["OnAdminViewRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnAdminBeforeCreateRequest().Add(func(e *core.AdminCreateEvent) error {
|
||||
t.EventCalls["OnAdminBeforeCreateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnAdminAfterCreateRequest().Add(func(e *core.AdminCreateEvent) error {
|
||||
t.EventCalls["OnAdminAfterCreateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnAdminBeforeUpdateRequest().Add(func(e *core.AdminUpdateEvent) error {
|
||||
t.EventCalls["OnAdminBeforeUpdateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnAdminAfterUpdateRequest().Add(func(e *core.AdminUpdateEvent) error {
|
||||
t.EventCalls["OnAdminAfterUpdateRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnAdminBeforeDeleteRequest().Add(func(e *core.AdminDeleteEvent) error {
|
||||
t.EventCalls["OnAdminBeforeDeleteRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnAdminAfterDeleteRequest().Add(func(e *core.AdminDeleteEvent) error {
|
||||
t.EventCalls["OnAdminAfterDeleteRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnAdminAuthRequest().Add(func(e *core.AdminAuthEvent) error {
|
||||
t.EventCalls["OnAdminAuthRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
t.OnFileDownloadRequest().Add(func(e *core.FileDownloadEvent) error {
|
||||
t.EventCalls["OnFileDownloadRequest"]++
|
||||
return nil
|
||||
})
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// NewTempDataDir creates a new temporary directory copy of the test data.
|
||||
//
|
||||
// It is the caller's responsibility to call `os.RemoveAll(dir)`
|
||||
// when the directory is no longer needed.
|
||||
func NewTempDataDir() (string, error) {
|
||||
tempDir, err := os.MkdirTemp("", "pb_test_*")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, currentFile, _, _ := runtime.Caller(0)
|
||||
testDataDir := filepath.Join(path.Dir(currentFile), "data")
|
||||
|
||||
// copy everything from testDataDir to tempDir
|
||||
if err := copyDir(testDataDir, tempDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tempDir, nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func copyDir(src string, dest string) error {
|
||||
if err := os.MkdirAll(dest, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceDir, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceDir.Close()
|
||||
|
||||
items, err := sourceDir.Readdir(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
fullSrcPath := filepath.Join(src, item.Name())
|
||||
fullDestPath := filepath.Join(dest, item.Name())
|
||||
|
||||
var copyErr error
|
||||
if item.IsDir() {
|
||||
copyErr = copyDir(fullSrcPath, fullDestPath)
|
||||
} else {
|
||||
copyErr = copyFile(fullSrcPath, fullDestPath)
|
||||
}
|
||||
|
||||
if copyErr != nil {
|
||||
return copyErr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(src string, dest string) error {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
destFile, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
if _, err := io.Copy(destFile, srcFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
@@ -0,0 +1 @@
|
||||
test file
|
||||
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"sFQDISxmvcjMxZf+32zV/g=="}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"n/jfHKrjRJEIh1wHLtCjQw=="}
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"mKtFIey+FX5Y7HWg+EAfNA=="}
|
||||
@@ -0,0 +1 @@
|
||||
test file
|
||||
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"sFQDISxmvcjMxZf+32zV/g=="}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"n/jfHKrjRJEIh1wHLtCjQw=="}
|
||||
@@ -0,0 +1 @@
|
||||
test file
|
||||
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"sFQDISxmvcjMxZf+32zV/g=="}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"n/jfHKrjRJEIh1wHLtCjQw=="}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"n/jfHKrjRJEIh1wHLtCjQw=="}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"n/jfHKrjRJEIh1wHLtCjQw=="}
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"mKtFIey+FX5Y7HWg+EAfNA=="}
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"mKtFIey+FX5Y7HWg+EAfNA=="}
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"mKtFIey+FX5Y7HWg+EAfNA=="}
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"mKtFIey+FX5Y7HWg+EAfNA=="}
|
||||
@@ -0,0 +1 @@
|
||||
test file
|
||||
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"sFQDISxmvcjMxZf+32zV/g=="}
|
||||
@@ -0,0 +1 @@
|
||||
test file
|
||||
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"sFQDISxmvcjMxZf+32zV/g=="}
|
||||
@@ -0,0 +1 @@
|
||||
test file
|
||||
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"text/plain; charset=utf-8","user.metadata":null,"md5":"sFQDISxmvcjMxZf+32zV/g=="}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"n/jfHKrjRJEIh1wHLtCjQw=="}
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"mKtFIey+FX5Y7HWg+EAfNA=="}
|
||||
@@ -0,0 +1,50 @@
|
||||
package tests
|
||||
|
||||
func MockRequestLogsData(app *TestApp) error {
|
||||
_, err := app.LogsDB().NewQuery(`
|
||||
delete from {{_requests}};
|
||||
|
||||
insert into {{_requests}} (
|
||||
[[id]],
|
||||
[[url]],
|
||||
[[method]],
|
||||
[[status]],
|
||||
[[auth]],
|
||||
[[ip]],
|
||||
[[referer]],
|
||||
[[userAgent]],
|
||||
[[meta]],
|
||||
[[created]],
|
||||
[[updated]]
|
||||
)
|
||||
values
|
||||
(
|
||||
"873f2133-9f38-44fb-bf82-c8f53b310d91",
|
||||
"/test1",
|
||||
"get",
|
||||
200,
|
||||
"guest",
|
||||
"127.0.0.1",
|
||||
"",
|
||||
"",
|
||||
"{}",
|
||||
"2022-05-01 10:00:00.123",
|
||||
"2022-05-01 10:00:00.123"
|
||||
),
|
||||
(
|
||||
"f2133873-44fb-9f38-bf82-c918f53b310d",
|
||||
"/test2",
|
||||
"post",
|
||||
400,
|
||||
"admin",
|
||||
"127.0.0.1",
|
||||
"",
|
||||
"",
|
||||
'{"errorDetails":"error_details..."}',
|
||||
"2022-05-02 10:00:00.123",
|
||||
"2022-05-02 10:00:00.123"
|
||||
);
|
||||
`).Execute()
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/mail"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
)
|
||||
|
||||
var _ mailer.Mailer = (*TestMailer)(nil)
|
||||
|
||||
// TestMailer is a mock `mailer.Mailer` implementation.
|
||||
type TestMailer struct {
|
||||
TotalSend int
|
||||
LastHtmlBody string
|
||||
}
|
||||
|
||||
// Reset clears any previously test collected data.
|
||||
func (m *TestMailer) Reset() {
|
||||
m.LastHtmlBody = ""
|
||||
m.TotalSend = 0
|
||||
}
|
||||
|
||||
// Send implements `mailer.Mailer` interface.
|
||||
func (m *TestMailer) Send(fromEmail mail.Address, toEmail mail.Address, subject string, html string, attachments map[string]io.Reader) error {
|
||||
m.LastHtmlBody = html
|
||||
m.TotalSend++
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
)
|
||||
|
||||
// MockMultipartData creates a mocked multipart/form-data payload.
|
||||
//
|
||||
// Example
|
||||
// data, mp, err := tests.MockMultipartData(
|
||||
// map[string]string{"title": "new"},
|
||||
// "file1",
|
||||
// "file2",
|
||||
// ...
|
||||
// )
|
||||
func MockMultipartData(data map[string]string, fileFields ...string) (*bytes.Buffer, *multipart.Writer, error) {
|
||||
body := new(bytes.Buffer)
|
||||
mp := multipart.NewWriter(body)
|
||||
defer mp.Close()
|
||||
|
||||
// write data fields
|
||||
for k, v := range data {
|
||||
mp.WriteField(k, v)
|
||||
}
|
||||
|
||||
// write file fields
|
||||
for _, fileField := range fileFields {
|
||||
// create a test temporary file
|
||||
tmpFile, err := os.CreateTemp(os.TempDir(), "tmpfile-*.txt")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err := tmpFile.Write([]byte("test")); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tmpFile.Seek(0, 0)
|
||||
defer tmpFile.Close()
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
// stub uploaded file
|
||||
w, err := mp.CreateFormFile(fileField, tmpFile.Name())
|
||||
if err != nil {
|
||||
return nil, mp, err
|
||||
}
|
||||
if _, err := io.Copy(w, tmpFile); err != nil {
|
||||
return nil, mp, err
|
||||
}
|
||||
}
|
||||
|
||||
return body, mp, nil
|
||||
}
|
||||