package models_test import ( "encoding/json" "testing" validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/tools/list" "github.com/pocketbase/pocketbase/tools/types" ) func TestCollectionTableName(t *testing.T) { m := models.Collection{} if m.TableName() != "_collections" { t.Fatalf("Unexpected table name, got %q", m.TableName()) } } func TestCollectionBaseFilesPath(t *testing.T) { m := models.Collection{} m.RefreshId() expected := m.Id if m.BaseFilesPath() != expected { t.Fatalf("Expected path %s, got %s", expected, m.BaseFilesPath()) } } func TestCollectionIsBase(t *testing.T) { scenarios := []struct { collection models.Collection expected bool }{ {models.Collection{}, false}, {models.Collection{Type: "unknown"}, false}, {models.Collection{Type: models.CollectionTypeBase}, true}, {models.Collection{Type: models.CollectionTypeAuth}, false}, } for i, s := range scenarios { result := s.collection.IsBase() if result != s.expected { t.Errorf("(%d) Expected %v, got %v", i, s.expected, result) } } } func TestCollectionIsAuth(t *testing.T) { scenarios := []struct { collection models.Collection expected bool }{ {models.Collection{}, false}, {models.Collection{Type: "unknown"}, false}, {models.Collection{Type: models.CollectionTypeBase}, false}, {models.Collection{Type: models.CollectionTypeAuth}, true}, } for i, s := range scenarios { result := s.collection.IsAuth() if result != s.expected { t.Errorf("(%d) Expected %v, got %v", i, s.expected, result) } } } func TestCollectionMarshalJSON(t *testing.T) { scenarios := []struct { name string collection models.Collection expected string }{ { "no type", models.Collection{Name: "test"}, `{"id":"","created":"","updated":"","name":"test","type":"","system":false,"schema":[],"indexes":[],"listRule":null,"viewRule":null,"createRule":null,"updateRule":null,"deleteRule":null,"options":{}}`, }, { "unknown type + non empty options", models.Collection{Name: "test", Type: "unknown", ListRule: types.Pointer("test_list"), Options: types.JsonMap{"test": 123}, Indexes: types.JsonArray[string]{"idx_test"}}, `{"id":"","created":"","updated":"","name":"test","type":"unknown","system":false,"schema":[],"indexes":["idx_test"],"listRule":"test_list","viewRule":null,"createRule":null,"updateRule":null,"deleteRule":null,"options":{}}`, }, { "base type + non empty options", models.Collection{Name: "test", Type: models.CollectionTypeBase, ListRule: types.Pointer("test_list"), Options: types.JsonMap{"test": 123}}, `{"id":"","created":"","updated":"","name":"test","type":"base","system":false,"schema":[],"indexes":[],"listRule":"test_list","viewRule":null,"createRule":null,"updateRule":null,"deleteRule":null,"options":{}}`, }, { "auth type + non empty options", models.Collection{BaseModel: models.BaseModel{Id: "test"}, Type: models.CollectionTypeAuth, Options: types.JsonMap{"test": 123, "allowOAuth2Auth": true, "minPasswordLength": 4}}, `{"id":"test","created":"","updated":"","name":"","type":"auth","system":false,"schema":[],"indexes":[],"listRule":null,"viewRule":null,"createRule":null,"updateRule":null,"deleteRule":null,"options":{"allowEmailAuth":false,"allowOAuth2Auth":true,"allowUsernameAuth":false,"exceptEmailDomains":null,"manageRule":null,"minPasswordLength":4,"onlyEmailDomains":null,"requireEmail":false}}`, }, } for _, s := range scenarios { result, err := s.collection.MarshalJSON() if err != nil { t.Errorf("[%s] Unexpected error %v", s.name, err) continue } if string(result) != s.expected { t.Errorf("[%s] Expected\n%v \ngot \n%v", s.name, s.expected, string(result)) } } } func TestCollectionBaseOptions(t *testing.T) { scenarios := []struct { name string collection models.Collection expected string }{ { "no type", models.Collection{Options: types.JsonMap{"test": 123}}, "{}", }, { "unknown type", models.Collection{Type: "anything", Options: types.JsonMap{"test": 123}}, "{}", }, { "different type", models.Collection{Type: models.CollectionTypeAuth, Options: types.JsonMap{"test": 123, "minPasswordLength": 4}}, "{}", }, { "base type", models.Collection{Type: models.CollectionTypeBase, Options: types.JsonMap{"test": 123}}, "{}", }, } for _, s := range scenarios { result := s.collection.BaseOptions() encoded, err := json.Marshal(result) if err != nil { t.Fatal(err) } if strEncoded := string(encoded); strEncoded != s.expected { t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded) } } } func TestCollectionAuthOptions(t *testing.T) { options := types.JsonMap{"test": 123, "minPasswordLength": 4} expectedSerialization := `{"manageRule":null,"allowOAuth2Auth":false,"allowUsernameAuth":false,"allowEmailAuth":false,"requireEmail":false,"exceptEmailDomains":null,"onlyEmailDomains":null,"minPasswordLength":4}` scenarios := []struct { name string collection models.Collection expected string }{ { "no type", models.Collection{Options: options}, expectedSerialization, }, { "unknown type", models.Collection{Type: "anything", Options: options}, expectedSerialization, }, { "different type", models.Collection{Type: models.CollectionTypeBase, Options: options}, expectedSerialization, }, { "auth type", models.Collection{Type: models.CollectionTypeAuth, Options: options}, expectedSerialization, }, } for _, s := range scenarios { result := s.collection.AuthOptions() encoded, err := json.Marshal(result) if err != nil { t.Fatal(err) } if strEncoded := string(encoded); strEncoded != s.expected { t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded) } } } func TestCollectionViewOptions(t *testing.T) { options := types.JsonMap{"query": "select id from demo1", "minPasswordLength": 4} expectedSerialization := `{"query":"select id from demo1"}` scenarios := []struct { name string collection models.Collection expected string }{ { "no type", models.Collection{Options: options}, expectedSerialization, }, { "unknown type", models.Collection{Type: "anything", Options: options}, expectedSerialization, }, { "different type", models.Collection{Type: models.CollectionTypeBase, Options: options}, expectedSerialization, }, { "view type", models.Collection{Type: models.CollectionTypeView, Options: options}, expectedSerialization, }, } for _, s := range scenarios { result := s.collection.ViewOptions() encoded, err := json.Marshal(result) if err != nil { t.Fatal(err) } if strEncoded := string(encoded); strEncoded != s.expected { t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded) } } } func TestNormalizeOptions(t *testing.T) { scenarios := []struct { name string collection models.Collection expected string // serialized options }{ { "unknown type", models.Collection{Type: "unknown", Options: types.JsonMap{"test": 123, "minPasswordLength": 4}}, "{}", }, { "base type", models.Collection{Type: models.CollectionTypeBase, Options: types.JsonMap{"test": 123, "minPasswordLength": 4}}, "{}", }, { "auth type", models.Collection{Type: models.CollectionTypeAuth, Options: types.JsonMap{"test": 123, "minPasswordLength": 4}}, `{"allowEmailAuth":false,"allowOAuth2Auth":false,"allowUsernameAuth":false,"exceptEmailDomains":null,"manageRule":null,"minPasswordLength":4,"onlyEmailDomains":null,"requireEmail":false}`, }, } for _, s := range scenarios { if err := s.collection.NormalizeOptions(); err != nil { t.Errorf("[%s] Unexpected error %v", s.name, err) continue } encoded, err := json.Marshal(s.collection.Options) if err != nil { t.Fatal(err) } if strEncoded := string(encoded); strEncoded != s.expected { t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded) } } } func TestDecodeOptions(t *testing.T) { m := models.Collection{ Options: types.JsonMap{"test": 123}, } result := struct { Test int }{} if err := m.DecodeOptions(&result); err != nil { t.Fatal(err) } if result.Test != 123 { t.Fatalf("Expected %v, got %v", 123, result.Test) } } func TestSetOptions(t *testing.T) { scenarios := []struct { name string collection models.Collection options any expected string // serialized options }{ { "no type", models.Collection{}, map[string]any{}, "{}", }, { "unknown type + non empty options", models.Collection{Type: "unknown", Options: types.JsonMap{"test": 123}}, map[string]any{"test": 456, "minPasswordLength": 4}, "{}", }, { "base type", models.Collection{Type: models.CollectionTypeBase, Options: types.JsonMap{"test": 123}}, map[string]any{"test": 456, "minPasswordLength": 4}, "{}", }, { "auth type", models.Collection{Type: models.CollectionTypeAuth, Options: types.JsonMap{"test": 123}}, map[string]any{"test": 456, "minPasswordLength": 4}, `{"allowEmailAuth":false,"allowOAuth2Auth":false,"allowUsernameAuth":false,"exceptEmailDomains":null,"manageRule":null,"minPasswordLength":4,"onlyEmailDomains":null,"requireEmail":false}`, }, } for _, s := range scenarios { if err := s.collection.SetOptions(s.options); err != nil { t.Errorf("[%s] Unexpected error %v", s.name, err) continue } encoded, err := json.Marshal(s.collection.Options) if err != nil { t.Fatal(err) } if strEncoded := string(encoded); strEncoded != s.expected { t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded) } } } func TestCollectionBaseOptionsValidate(t *testing.T) { opt := models.CollectionBaseOptions{} if err := opt.Validate(); err != nil { t.Fatal(err) } } func TestCollectionAuthOptionsValidate(t *testing.T) { scenarios := []struct { name string options models.CollectionAuthOptions expectedErrors []string }{ { "empty", models.CollectionAuthOptions{}, nil, }, { "empty string ManageRule", models.CollectionAuthOptions{ManageRule: types.Pointer("")}, []string{"manageRule"}, }, { "minPasswordLength < 5", models.CollectionAuthOptions{MinPasswordLength: 3}, []string{"minPasswordLength"}, }, { "minPasswordLength > 72", models.CollectionAuthOptions{MinPasswordLength: 73}, []string{"minPasswordLength"}, }, { "both OnlyDomains and ExceptDomains set", models.CollectionAuthOptions{ OnlyEmailDomains: []string{"example.com", "test.com"}, ExceptEmailDomains: []string{"example.com", "test.com"}, }, []string{"onlyEmailDomains", "exceptEmailDomains"}, }, { "only OnlyDomains set", models.CollectionAuthOptions{ OnlyEmailDomains: []string{"example.com", "test.com"}, }, []string{}, }, { "only ExceptEmailDomains set", models.CollectionAuthOptions{ ExceptEmailDomains: []string{"example.com", "test.com"}, }, []string{}, }, { "all fields with valid data", models.CollectionAuthOptions{ ManageRule: types.Pointer("test"), AllowOAuth2Auth: true, AllowUsernameAuth: true, AllowEmailAuth: true, RequireEmail: true, ExceptEmailDomains: []string{"example.com", "test.com"}, OnlyEmailDomains: nil, MinPasswordLength: 5, }, []string{}, }, } for _, s := range scenarios { result := s.options.Validate() // parse errors errs, ok := result.(validation.Errors) if !ok && result != nil { t.Errorf("[%s] Failed to parse errors %v", s.name, result) continue } if len(errs) != len(s.expectedErrors) { t.Errorf("[%s] Expected error keys %v, got errors \n%v", s.name, s.expectedErrors, result) continue } for key := range errs { if !list.ExistInSlice(key, s.expectedErrors) { t.Errorf("[%s] Unexpected error key %q in \n%v", s.name, key, errs) } } } } func TestCollectionViewOptionsValidate(t *testing.T) { scenarios := []struct { name string options models.CollectionViewOptions expectedErrors []string }{ { "empty", models.CollectionViewOptions{}, []string{"query"}, }, { "valid data", models.CollectionViewOptions{ Query: "test123", }, []string{}, }, } for _, s := range scenarios { result := s.options.Validate() // parse errors errs, ok := result.(validation.Errors) if !ok && result != nil { t.Errorf("[%s] Failed to parse errors %v", s.name, result) continue } if len(errs) != len(s.expectedErrors) { t.Errorf("[%s] Expected error keys %v, got errors \n%v", s.name, s.expectedErrors, result) continue } for key := range errs { if !list.ExistInSlice(key, s.expectedErrors) { t.Errorf("[%s] Unexpected error key %q in \n%v", s.name, key, errs) } } } }