merge v0.23.0-rc changes
This commit is contained in:
@@ -0,0 +1,691 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user