mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-01-27 23:46:18 +02:00
692 lines
14 KiB
Go
692 lines
14 KiB
Go
package core_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pocketbase/pocketbase/core"
|
|
"github.com/pocketbase/pocketbase/tests"
|
|
"github.com/pocketbase/pocketbase/tools/mailer"
|
|
)
|
|
|
|
func TestSettingsDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
err := app.Delete(app.Settings())
|
|
if err == nil {
|
|
t.Fatal("Exected settings delete to fail")
|
|
}
|
|
}
|
|
|
|
func TestSettingsMerge(t *testing.T) {
|
|
s1 := &core.Settings{}
|
|
s1.Meta.AppURL = "app_url" // should be unset
|
|
|
|
s2 := &core.Settings{}
|
|
s2.Meta.AppName = "test"
|
|
s2.Logs.MaxDays = 123
|
|
s2.SMTP.Host = "test"
|
|
s2.SMTP.Enabled = true
|
|
s2.S3.Enabled = true
|
|
s2.S3.Endpoint = "test"
|
|
s2.Backups.Cron = "* * * * *"
|
|
s2.Batch.Timeout = 15
|
|
|
|
if err := s1.Merge(s2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
s1Encoded, err := json.Marshal(s1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
s2Encoded, err := json.Marshal(s2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if string(s1Encoded) != string(s2Encoded) {
|
|
t.Fatalf("Expected the same serialization, got\n%v\nVS\n%v", string(s1Encoded), string(s2Encoded))
|
|
}
|
|
}
|
|
|
|
func TestSettingsClone(t *testing.T) {
|
|
s1 := &core.Settings{}
|
|
s1.Meta.AppName = "test_name"
|
|
|
|
s2, err := s1.Clone()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
s1Bytes, err := json.Marshal(s1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
s2Bytes, err := json.Marshal(s2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if string(s1Bytes) != string(s2Bytes) {
|
|
t.Fatalf("Expected equivalent serialization, got %v VS %v", string(s1Bytes), string(s2Bytes))
|
|
}
|
|
|
|
// verify that it is a deep copy
|
|
s2.Meta.AppName = "new_test_name"
|
|
if s1.Meta.AppName == s2.Meta.AppName {
|
|
t.Fatalf("Expected s1 and s2 to have different Meta.AppName, got %s", s1.Meta.AppName)
|
|
}
|
|
}
|
|
|
|
func TestSettingsMarshalJSON(t *testing.T) {
|
|
settings := &core.Settings{}
|
|
|
|
// control fields
|
|
settings.Meta.AppName = "test123"
|
|
settings.SMTP.Username = "abc"
|
|
|
|
// secrets
|
|
testSecret := "test_secret"
|
|
settings.SMTP.Password = testSecret
|
|
settings.S3.Secret = testSecret
|
|
settings.Backups.S3.Secret = testSecret
|
|
|
|
raw, err := json.Marshal(settings)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rawStr := string(raw)
|
|
|
|
expected := `{"smtp":{"enabled":false,"port":0,"host":"","username":"abc","authMethod":"","tls":false,"localName":""},"backups":{"cron":"","cronMaxKeep":0,"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","forcePathStyle":false}},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","forcePathStyle":false},"meta":{"appName":"test123","appURL":"","senderName":"","senderAddress":"","hideControls":false},"logs":{"maxDays":0,"minLevel":0,"logIP":false,"logAuthId":false},"batch":{"enabled":false,"maxRequests":0,"timeout":0,"maxBodySize":0},"rateLimits":{"rules":[],"enabled":false},"trustedProxy":{"headers":[],"useLeftmostIP":false}}`
|
|
|
|
if rawStr != expected {
|
|
t.Fatalf("Expected\n%v\ngot\n%v", expected, rawStr)
|
|
}
|
|
}
|
|
|
|
func TestSettingsValidate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
s := app.Settings()
|
|
|
|
// set invalid settings data
|
|
s.Meta.AppName = ""
|
|
s.Logs.MaxDays = -10
|
|
s.SMTP.Enabled = true
|
|
s.SMTP.Host = ""
|
|
s.S3.Enabled = true
|
|
s.S3.Endpoint = "invalid"
|
|
s.Backups.Cron = "invalid"
|
|
s.Backups.CronMaxKeep = -10
|
|
s.Batch.Enabled = true
|
|
s.Batch.MaxRequests = -1
|
|
s.Batch.Timeout = -1
|
|
s.RateLimits.Enabled = true
|
|
s.RateLimits.Rules = nil
|
|
|
|
// check if Validate() is triggering the members validate methods.
|
|
err := app.Validate(s)
|
|
if err == nil {
|
|
t.Fatalf("Expected error, got nil")
|
|
}
|
|
|
|
expectations := []string{
|
|
`"meta":{`,
|
|
`"logs":{`,
|
|
`"smtp":{`,
|
|
`"s3":{`,
|
|
`"backups":{`,
|
|
`"batch":{`,
|
|
`"rateLimits":{`,
|
|
}
|
|
|
|
errBytes, _ := json.Marshal(err)
|
|
jsonErr := string(errBytes)
|
|
for _, expected := range expectations {
|
|
if !strings.Contains(jsonErr, expected) {
|
|
t.Errorf("Expected error key %s in %v", expected, jsonErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMetaConfigValidate(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
config core.MetaConfig
|
|
expectedErrors []string
|
|
}{
|
|
{
|
|
"zero values",
|
|
core.MetaConfig{},
|
|
[]string{
|
|
"appName",
|
|
"appURL",
|
|
"senderName",
|
|
"senderAddress",
|
|
},
|
|
},
|
|
{
|
|
"invalid data",
|
|
core.MetaConfig{
|
|
AppName: strings.Repeat("a", 300),
|
|
AppURL: "test",
|
|
SenderName: strings.Repeat("a", 300),
|
|
SenderAddress: "invalid_email",
|
|
},
|
|
[]string{
|
|
"appName",
|
|
"appURL",
|
|
"senderName",
|
|
"senderAddress",
|
|
},
|
|
},
|
|
{
|
|
"valid data",
|
|
core.MetaConfig{
|
|
AppName: "test",
|
|
AppURL: "https://example.com",
|
|
SenderName: "test",
|
|
SenderAddress: "test@example.com",
|
|
},
|
|
[]string{},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
result := s.config.Validate()
|
|
|
|
tests.TestValidationErrors(t, result, s.expectedErrors)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLogsConfigValidate(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
config core.LogsConfig
|
|
expectedErrors []string
|
|
}{
|
|
{
|
|
"zero values",
|
|
core.LogsConfig{},
|
|
[]string{},
|
|
},
|
|
{
|
|
"invalid data",
|
|
core.LogsConfig{MaxDays: -1},
|
|
[]string{"maxDays"},
|
|
},
|
|
{
|
|
"valid data",
|
|
core.LogsConfig{MaxDays: 2},
|
|
[]string{},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
result := s.config.Validate()
|
|
|
|
tests.TestValidationErrors(t, result, s.expectedErrors)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSMTPConfigValidate(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
config core.SMTPConfig
|
|
expectedErrors []string
|
|
}{
|
|
{
|
|
"zero values (disabled)",
|
|
core.SMTPConfig{},
|
|
[]string{},
|
|
},
|
|
{
|
|
"zero values (enabled)",
|
|
core.SMTPConfig{Enabled: true},
|
|
[]string{"host", "port"},
|
|
},
|
|
{
|
|
"invalid data",
|
|
core.SMTPConfig{
|
|
Enabled: true,
|
|
Host: "test:test:test",
|
|
Port: -10,
|
|
LocalName: "invalid!",
|
|
AuthMethod: "invalid",
|
|
},
|
|
[]string{"host", "port", "authMethod", "localName"},
|
|
},
|
|
{
|
|
"valid data (no explicit auth method and localName)",
|
|
core.SMTPConfig{
|
|
Enabled: true,
|
|
Host: "example.com",
|
|
Port: 100,
|
|
TLS: true,
|
|
},
|
|
[]string{},
|
|
},
|
|
{
|
|
"valid data (explicit auth method and localName)",
|
|
core.SMTPConfig{
|
|
Enabled: true,
|
|
Host: "example.com",
|
|
Port: 100,
|
|
AuthMethod: mailer.SMTPAuthLogin,
|
|
LocalName: "example.com",
|
|
},
|
|
[]string{},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
result := s.config.Validate()
|
|
|
|
tests.TestValidationErrors(t, result, s.expectedErrors)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestS3ConfigValidate(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
config core.S3Config
|
|
expectedErrors []string
|
|
}{
|
|
{
|
|
"zero values (disabled)",
|
|
core.S3Config{},
|
|
[]string{},
|
|
},
|
|
{
|
|
"zero values (enabled)",
|
|
core.S3Config{Enabled: true},
|
|
[]string{
|
|
"bucket",
|
|
"region",
|
|
"endpoint",
|
|
"accessKey",
|
|
"secret",
|
|
},
|
|
},
|
|
{
|
|
"invalid data",
|
|
core.S3Config{
|
|
Enabled: true,
|
|
Endpoint: "test:test:test",
|
|
},
|
|
[]string{
|
|
"bucket",
|
|
"region",
|
|
"endpoint",
|
|
"accessKey",
|
|
"secret",
|
|
},
|
|
},
|
|
{
|
|
"valid data (url endpoint)",
|
|
core.S3Config{
|
|
Enabled: true,
|
|
Endpoint: "https://localhost:8090",
|
|
Bucket: "test",
|
|
Region: "test",
|
|
AccessKey: "test",
|
|
Secret: "test",
|
|
},
|
|
[]string{},
|
|
},
|
|
{
|
|
"valid data (hostname endpoint)",
|
|
core.S3Config{
|
|
Enabled: true,
|
|
Endpoint: "example.com",
|
|
Bucket: "test",
|
|
Region: "test",
|
|
AccessKey: "test",
|
|
Secret: "test",
|
|
},
|
|
[]string{},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
result := s.config.Validate()
|
|
|
|
tests.TestValidationErrors(t, result, s.expectedErrors)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBackupsConfigValidate(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
config core.BackupsConfig
|
|
expectedErrors []string
|
|
}{
|
|
{
|
|
"zero value",
|
|
core.BackupsConfig{},
|
|
[]string{},
|
|
},
|
|
{
|
|
"invalid cron",
|
|
core.BackupsConfig{
|
|
Cron: "invalid",
|
|
CronMaxKeep: 0,
|
|
},
|
|
[]string{"cron", "cronMaxKeep"},
|
|
},
|
|
{
|
|
"invalid enabled S3",
|
|
core.BackupsConfig{
|
|
S3: core.S3Config{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
[]string{"s3"},
|
|
},
|
|
{
|
|
"valid data",
|
|
core.BackupsConfig{
|
|
S3: core.S3Config{
|
|
Enabled: true,
|
|
Endpoint: "example.com",
|
|
Bucket: "test",
|
|
Region: "test",
|
|
AccessKey: "test",
|
|
Secret: "test",
|
|
},
|
|
Cron: "*/10 * * * *",
|
|
CronMaxKeep: 1,
|
|
},
|
|
[]string{},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
result := s.config.Validate()
|
|
|
|
tests.TestValidationErrors(t, result, s.expectedErrors)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBatchConfigValidate(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
config core.BatchConfig
|
|
expectedErrors []string
|
|
}{
|
|
{
|
|
"zero value",
|
|
core.BatchConfig{},
|
|
[]string{},
|
|
},
|
|
{
|
|
"zero value (enabled)",
|
|
core.BatchConfig{Enabled: true},
|
|
[]string{"maxRequests", "timeout"},
|
|
},
|
|
{
|
|
"invalid data (negative values)",
|
|
core.BatchConfig{
|
|
MaxRequests: -1,
|
|
Timeout: -1,
|
|
MaxBodySize: -1,
|
|
},
|
|
[]string{"maxRequests", "timeout", "maxBodySize"},
|
|
},
|
|
{
|
|
"min fields valid data",
|
|
core.BatchConfig{
|
|
Enabled: true,
|
|
MaxRequests: 1,
|
|
Timeout: 1,
|
|
},
|
|
[]string{},
|
|
},
|
|
{
|
|
"all fields valid data",
|
|
core.BatchConfig{
|
|
Enabled: true,
|
|
MaxRequests: 10,
|
|
Timeout: 1,
|
|
MaxBodySize: 1,
|
|
},
|
|
[]string{},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
result := s.config.Validate()
|
|
|
|
tests.TestValidationErrors(t, result, s.expectedErrors)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRateLimitsConfigValidate(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
config core.RateLimitsConfig
|
|
expectedErrors []string
|
|
}{
|
|
{
|
|
"zero value (disabled)",
|
|
core.RateLimitsConfig{},
|
|
[]string{},
|
|
},
|
|
{
|
|
"zero value (enabled)",
|
|
core.RateLimitsConfig{Enabled: true},
|
|
[]string{"rules"},
|
|
},
|
|
{
|
|
"invalid data",
|
|
core.RateLimitsConfig{
|
|
Enabled: true,
|
|
Rules: []core.RateLimitRule{
|
|
{
|
|
Label: "/123abc/",
|
|
Duration: 1,
|
|
MaxRequests: 2,
|
|
},
|
|
{
|
|
Label: "!abc",
|
|
Duration: -1,
|
|
MaxRequests: -1,
|
|
},
|
|
},
|
|
},
|
|
[]string{"rules"},
|
|
},
|
|
{
|
|
"valid data",
|
|
core.RateLimitsConfig{
|
|
Enabled: true,
|
|
Rules: []core.RateLimitRule{
|
|
{
|
|
Label: "123_abc",
|
|
Duration: 1,
|
|
MaxRequests: 2,
|
|
},
|
|
{
|
|
Label: "/456-abc",
|
|
Duration: 1,
|
|
MaxRequests: 2,
|
|
},
|
|
},
|
|
},
|
|
[]string{},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
result := s.config.Validate()
|
|
|
|
tests.TestValidationErrors(t, result, s.expectedErrors)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRateLimitsFindRateLimitRule(t *testing.T) {
|
|
limits := core.RateLimitsConfig{
|
|
Rules: []core.RateLimitRule{
|
|
{Label: "abc"},
|
|
{Label: "POST /test/a/"},
|
|
{Label: "/test/a/"},
|
|
{Label: "POST /test/a"},
|
|
{Label: "/test/a"},
|
|
},
|
|
}
|
|
|
|
scenarios := []struct {
|
|
labels []string
|
|
expected string
|
|
}{
|
|
{[]string{}, ""},
|
|
{[]string{"missing"}, ""},
|
|
{[]string{"abc"}, "abc"},
|
|
{[]string{"/test"}, ""},
|
|
{[]string{"/test/a"}, "/test/a"},
|
|
{[]string{"GET /test/a"}, ""},
|
|
{[]string{"POST /test/a"}, "POST /test/a"},
|
|
{[]string{"/test/a/b/c"}, "/test/a/"},
|
|
{[]string{"GET /test/a/b/c"}, ""},
|
|
{[]string{"POST /test/a/b/c"}, "POST /test/a/"},
|
|
{[]string{"/test/a", "abc"}, "/test/a"}, // priority checks
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(strings.Join(s.labels, ""), func(t *testing.T) {
|
|
rule, ok := limits.FindRateLimitRule(s.labels)
|
|
|
|
hasLabel := rule.Label != ""
|
|
if hasLabel != ok {
|
|
t.Fatalf("Expected hasLabel %v, got %v", hasLabel, ok)
|
|
}
|
|
|
|
if rule.Label != s.expected {
|
|
t.Fatalf("Expected rule with label %q, got %q", s.expected, rule.Label)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRateLimitRuleValidate(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
config core.RateLimitRule
|
|
expectedErrors []string
|
|
}{
|
|
{
|
|
"zero value",
|
|
core.RateLimitRule{},
|
|
[]string{"label", "duration", "maxRequests"},
|
|
},
|
|
{
|
|
"invalid data",
|
|
core.RateLimitRule{
|
|
Label: "@abc",
|
|
Duration: -1,
|
|
MaxRequests: -1,
|
|
},
|
|
[]string{"label", "duration", "maxRequests"},
|
|
},
|
|
{
|
|
"valid data (name)",
|
|
core.RateLimitRule{
|
|
Label: "abc:123",
|
|
Duration: 1,
|
|
MaxRequests: 1,
|
|
},
|
|
[]string{},
|
|
},
|
|
{
|
|
"valid data (name:action)",
|
|
core.RateLimitRule{
|
|
Label: "abc:123",
|
|
Duration: 1,
|
|
MaxRequests: 1,
|
|
},
|
|
[]string{},
|
|
},
|
|
{
|
|
"valid data (*:action)",
|
|
core.RateLimitRule{
|
|
Label: "*:123",
|
|
Duration: 1,
|
|
MaxRequests: 1,
|
|
},
|
|
[]string{},
|
|
},
|
|
{
|
|
"valid data (path /a/b)",
|
|
core.RateLimitRule{
|
|
Label: "/a/b",
|
|
Duration: 1,
|
|
MaxRequests: 1,
|
|
},
|
|
[]string{},
|
|
},
|
|
{
|
|
"valid data (path POST /a/b)",
|
|
core.RateLimitRule{
|
|
Label: "POST /a/b/",
|
|
Duration: 1,
|
|
MaxRequests: 1,
|
|
},
|
|
[]string{},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
result := s.config.Validate()
|
|
|
|
tests.TestValidationErrors(t, result, s.expectedErrors)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRateLimitRuleDurationTime(t *testing.T) {
|
|
scenarios := []struct {
|
|
config core.RateLimitRule
|
|
expected time.Duration
|
|
}{
|
|
{core.RateLimitRule{}, 0 * time.Second},
|
|
{core.RateLimitRule{Duration: 1234}, 1234 * time.Second},
|
|
}
|
|
|
|
for i, s := range scenarios {
|
|
t.Run(fmt.Sprintf("%d_%d", i, s.config.Duration), func(t *testing.T) {
|
|
result := s.config.DurationTime()
|
|
|
|
if result != s.expected {
|
|
t.Fatalf("Expected duration %d, got %d", s.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|