mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-02-14 17:00:06 +02:00
381 lines
9.0 KiB
Go
381 lines
9.0 KiB
Go
package core_test
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"log/slog"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "unsafe"
|
|
|
|
"github.com/pocketbase/dbx"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
"github.com/pocketbase/pocketbase/tests"
|
|
"github.com/pocketbase/pocketbase/tools/logger"
|
|
"github.com/pocketbase/pocketbase/tools/mailer"
|
|
)
|
|
|
|
func TestNewBaseApp(t *testing.T) {
|
|
const testDataDir = "./pb_base_app_test_data_dir/"
|
|
defer os.RemoveAll(testDataDir)
|
|
|
|
app := core.NewBaseApp(core.BaseAppConfig{
|
|
DataDir: testDataDir,
|
|
EncryptionEnv: "test_env",
|
|
IsDev: true,
|
|
})
|
|
|
|
if app.DataDir() != testDataDir {
|
|
t.Fatalf("expected DataDir %q, got %q", testDataDir, app.DataDir())
|
|
}
|
|
|
|
if app.EncryptionEnv() != "test_env" {
|
|
t.Fatalf("expected EncryptionEnv test_env, got %q", app.EncryptionEnv())
|
|
}
|
|
|
|
if !app.IsDev() {
|
|
t.Fatalf("expected IsDev true, got %v", app.IsDev())
|
|
}
|
|
|
|
if app.Store() == nil {
|
|
t.Fatal("expected Store to be set, got nil")
|
|
}
|
|
|
|
if app.Settings() == nil {
|
|
t.Fatal("expected Settings to be set, got nil")
|
|
}
|
|
|
|
if app.SubscriptionsBroker() == nil {
|
|
t.Fatal("expected SubscriptionsBroker to be set, got nil")
|
|
}
|
|
|
|
if app.Cron() == nil {
|
|
t.Fatal("expected Cron to be set, got nil")
|
|
}
|
|
}
|
|
|
|
func TestBaseAppBootstrap(t *testing.T) {
|
|
const testDataDir = "./pb_base_app_test_data_dir/"
|
|
defer os.RemoveAll(testDataDir)
|
|
|
|
app := core.NewBaseApp(core.BaseAppConfig{
|
|
DataDir: testDataDir,
|
|
})
|
|
defer app.ResetBootstrapState()
|
|
|
|
if app.IsBootstrapped() {
|
|
t.Fatal("Didn't expect the application to be bootstrapped.")
|
|
}
|
|
|
|
if err := app.Bootstrap(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !app.IsBootstrapped() {
|
|
t.Fatal("Expected the application to be bootstrapped.")
|
|
}
|
|
|
|
if stat, err := os.Stat(testDataDir); err != nil || !stat.IsDir() {
|
|
t.Fatal("Expected test data directory to be created.")
|
|
}
|
|
|
|
type nilCheck struct {
|
|
name string
|
|
value any
|
|
expectNil bool
|
|
}
|
|
|
|
runNilChecks := func(checks []nilCheck) {
|
|
for _, check := range checks {
|
|
t.Run(check.name, func(t *testing.T) {
|
|
isNil := check.value == nil
|
|
if isNil != check.expectNil {
|
|
t.Fatalf("Expected isNil %v, got %v", check.expectNil, isNil)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
nilChecksBeforeReset := []nilCheck{
|
|
{"[before] concurrentDB", app.DB(), false},
|
|
{"[before] nonconcurrentDB", app.NonconcurrentDB(), false},
|
|
{"[before] auxConcurrentDB", app.AuxDB(), false},
|
|
{"[before] auxNonconcurrentDB", app.AuxNonconcurrentDB(), false},
|
|
{"[before] settings", app.Settings(), false},
|
|
{"[before] logger", app.Logger(), false},
|
|
{"[before] cached collections", app.Store().Get(core.StoreKeyCachedCollections), false},
|
|
}
|
|
|
|
runNilChecks(nilChecksBeforeReset)
|
|
|
|
// reset
|
|
if err := app.ResetBootstrapState(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
nilChecksAfterReset := []nilCheck{
|
|
{"[after] concurrentDB", app.DB(), true},
|
|
{"[after] nonconcurrentDB", app.NonconcurrentDB(), true},
|
|
{"[after] auxConcurrentDB", app.AuxDB(), true},
|
|
{"[after] auxNonconcurrentDB", app.AuxNonconcurrentDB(), true},
|
|
{"[after] settings", app.Settings(), false},
|
|
{"[after] logger", app.Logger(), false},
|
|
{"[after] cached collections", app.Store().Get(core.StoreKeyCachedCollections), false},
|
|
}
|
|
|
|
runNilChecks(nilChecksAfterReset)
|
|
}
|
|
|
|
func TestNewBaseAppIsTransactional(t *testing.T) {
|
|
const testDataDir = "./pb_base_app_test_data_dir/"
|
|
defer os.RemoveAll(testDataDir)
|
|
|
|
app := core.NewBaseApp(core.BaseAppConfig{
|
|
DataDir: testDataDir,
|
|
})
|
|
defer app.ResetBootstrapState()
|
|
|
|
if err := app.Bootstrap(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if app.IsTransactional() {
|
|
t.Fatalf("Didn't expect the app to be transactional")
|
|
}
|
|
|
|
app.RunInTransaction(func(txApp core.App) error {
|
|
if !txApp.IsTransactional() {
|
|
t.Fatalf("Expected the app to be transactional")
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestBaseAppNewMailClient(t *testing.T) {
|
|
const testDataDir = "./pb_base_app_test_data_dir/"
|
|
defer os.RemoveAll(testDataDir)
|
|
|
|
app := core.NewBaseApp(core.BaseAppConfig{
|
|
DataDir: testDataDir,
|
|
EncryptionEnv: "pb_test_env",
|
|
})
|
|
defer app.ResetBootstrapState()
|
|
|
|
client1 := app.NewMailClient()
|
|
m1, ok := client1.(*mailer.Sendmail)
|
|
if !ok {
|
|
t.Fatalf("Expected mailer.Sendmail instance, got %v", m1)
|
|
}
|
|
if m1.OnSend() == nil || m1.OnSend().Length() == 0 {
|
|
t.Fatal("Expected OnSend hook to be registered")
|
|
}
|
|
|
|
app.Settings().SMTP.Enabled = true
|
|
|
|
client2 := app.NewMailClient()
|
|
m2, ok := client2.(*mailer.SMTPClient)
|
|
if !ok {
|
|
t.Fatalf("Expected mailer.SMTPClient instance, got %v", m2)
|
|
}
|
|
if m2.OnSend() == nil || m2.OnSend().Length() == 0 {
|
|
t.Fatal("Expected OnSend hook to be registered")
|
|
}
|
|
}
|
|
|
|
func TestBaseAppNewFilesystem(t *testing.T) {
|
|
const testDataDir = "./pb_base_app_test_data_dir/"
|
|
defer os.RemoveAll(testDataDir)
|
|
|
|
app := core.NewBaseApp(core.BaseAppConfig{
|
|
DataDir: testDataDir,
|
|
})
|
|
defer app.ResetBootstrapState()
|
|
|
|
// local
|
|
local, localErr := app.NewFilesystem()
|
|
if localErr != nil {
|
|
t.Fatal(localErr)
|
|
}
|
|
if local == nil {
|
|
t.Fatal("Expected local filesystem instance, got nil")
|
|
}
|
|
|
|
// misconfigured s3
|
|
app.Settings().S3.Enabled = true
|
|
s3, s3Err := app.NewFilesystem()
|
|
if s3Err == nil {
|
|
t.Fatal("Expected S3 error, got nil")
|
|
}
|
|
if s3 != nil {
|
|
t.Fatalf("Expected nil s3 filesystem, got %v", s3)
|
|
}
|
|
}
|
|
|
|
func TestBaseAppNewBackupsFilesystem(t *testing.T) {
|
|
const testDataDir = "./pb_base_app_test_data_dir/"
|
|
defer os.RemoveAll(testDataDir)
|
|
|
|
app := core.NewBaseApp(core.BaseAppConfig{
|
|
DataDir: testDataDir,
|
|
})
|
|
defer app.ResetBootstrapState()
|
|
|
|
// local
|
|
local, localErr := app.NewBackupsFilesystem()
|
|
if localErr != nil {
|
|
t.Fatal(localErr)
|
|
}
|
|
if local == nil {
|
|
t.Fatal("Expected local backups filesystem instance, got nil")
|
|
}
|
|
|
|
// misconfigured s3
|
|
app.Settings().Backups.S3.Enabled = true
|
|
s3, s3Err := app.NewBackupsFilesystem()
|
|
if s3Err == nil {
|
|
t.Fatal("Expected S3 error, got nil")
|
|
}
|
|
if s3 != nil {
|
|
t.Fatalf("Expected nil s3 backups filesystem, got %v", s3)
|
|
}
|
|
}
|
|
|
|
func TestBaseAppLoggerWrites(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
// reset
|
|
if err := app.DeleteOldLogs(time.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
const logsThreshold = 200
|
|
|
|
totalLogs := func(app core.App, t *testing.T) int {
|
|
var total int
|
|
|
|
err := app.LogQuery().Select("count(*)").Row(&total)
|
|
if err != nil {
|
|
t.Fatalf("Failed to fetch total logs: %v", err)
|
|
}
|
|
|
|
return total
|
|
}
|
|
|
|
t.Run("disabled logs retention", func(t *testing.T) {
|
|
app.Settings().Logs.MaxDays = 0
|
|
|
|
for i := 0; i < logsThreshold+1; i++ {
|
|
app.Logger().Error("test")
|
|
}
|
|
|
|
if total := totalLogs(app, t); total != 0 {
|
|
t.Fatalf("Expected no logs, got %d", total)
|
|
}
|
|
})
|
|
|
|
t.Run("test batch logs writes", func(t *testing.T) {
|
|
app.Settings().Logs.MaxDays = 1
|
|
|
|
for i := 0; i < logsThreshold-1; i++ {
|
|
app.Logger().Error("test")
|
|
}
|
|
|
|
if total := totalLogs(app, t); total != 0 {
|
|
t.Fatalf("Expected no logs, got %d", total)
|
|
}
|
|
|
|
// should trigger batch write
|
|
app.Logger().Error("test")
|
|
|
|
// should be added for the next batch write
|
|
app.Logger().Error("test")
|
|
|
|
if total := totalLogs(app, t); total != logsThreshold {
|
|
t.Fatalf("Expected %d logs, got %d", logsThreshold, total)
|
|
}
|
|
|
|
// wait for ~3 secs to check the timer trigger
|
|
time.Sleep(3200 * time.Millisecond)
|
|
if total := totalLogs(app, t); total != logsThreshold+1 {
|
|
t.Fatalf("Expected %d logs, got %d", logsThreshold+1, total)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestBaseAppRefreshSettingsLoggerMinLevelEnabled(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
isDev bool
|
|
level int
|
|
// level->enabled map
|
|
expectations map[int]bool
|
|
}{
|
|
{
|
|
"dev mode",
|
|
true,
|
|
4,
|
|
map[int]bool{
|
|
3: true,
|
|
4: true,
|
|
5: true,
|
|
},
|
|
},
|
|
{
|
|
"nondev mode",
|
|
false,
|
|
4,
|
|
map[int]bool{
|
|
3: false,
|
|
4: true,
|
|
5: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
const testDataDir = "./pb_base_app_test_data_dir/"
|
|
defer os.RemoveAll(testDataDir)
|
|
|
|
app := core.NewBaseApp(core.BaseAppConfig{
|
|
DataDir: testDataDir,
|
|
IsDev: s.isDev,
|
|
})
|
|
defer app.ResetBootstrapState()
|
|
|
|
if err := app.Bootstrap(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// silence query logs
|
|
app.DB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {}
|
|
app.DB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {}
|
|
app.NonconcurrentDB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {}
|
|
app.NonconcurrentDB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {}
|
|
|
|
handler, ok := app.Logger().Handler().(*logger.BatchHandler)
|
|
if !ok {
|
|
t.Fatalf("Expected BatchHandler, got %v", app.Logger().Handler())
|
|
}
|
|
|
|
app.Settings().Logs.MinLevel = s.level
|
|
|
|
if err := app.Save(app.Settings()); err != nil {
|
|
t.Fatalf("Failed to save settings: %v", err)
|
|
}
|
|
|
|
for level, enabled := range s.expectations {
|
|
if v := handler.Enabled(context.Background(), slog.Level(level)); v != enabled {
|
|
t.Fatalf("Expected level %d Enabled() to be %v, got %v", level, enabled, v)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|