package core_test import ( "slices" "strings" "testing" "github.com/pocketbase/pocketbase/core" ) func TestNewFieldsList(t *testing.T) { fields := core.NewFieldsList( &core.TextField{Id: "id1", Name: "test1"}, &core.TextField{Name: "test2"}, &core.TextField{Id: "id1", Name: "test1_new"}, // should replace the original id1 field ) if len(fields) != 2 { t.Fatalf("Expected 2 fields, got %d (%v)", len(fields), fields) } for _, f := range fields { if f.GetId() == "" { t.Fatalf("Expected field id to be set, found empty id for field %v", f) } } if fields[0].GetName() != "test1_new" { t.Fatalf("Expected field with name test1_new, got %s", fields[0].GetName()) } if fields[1].GetName() != "test2" { t.Fatalf("Expected field with name test2, got %s", fields[1].GetName()) } } func TestFieldsListClone(t *testing.T) { f1 := &core.TextField{Name: "test1"} f2 := &core.EmailField{Name: "test2"} s1 := core.NewFieldsList(f1, f2) s2, err := s1.Clone() if err != nil { t.Fatal(err) } s1Str := s1.String() s2Str := s2.String() if s1Str != s2Str { t.Fatalf("Expected the cloned list to be equal, got \n%v\nVS\n%v", s1, s2) } // change in one list shouldn't result to change in the other // (aka. check if it is a deep clone) s1[0].SetName("test1_update") if s2[0].GetName() != "test1" { t.Fatalf("Expected s2 field name to not change, got %q", s2[0].GetName()) } } func TestFieldsListFieldNames(t *testing.T) { f1 := &core.TextField{Name: "test1"} f2 := &core.EmailField{Name: "test2"} testFieldsList := core.NewFieldsList(f1, f2) result := testFieldsList.FieldNames() expected := []string{f1.Name, f2.Name} if len(result) != len(expected) { t.Fatalf("Expected %d slice elements, got %d\n%v", len(expected), len(result), result) } for _, name := range expected { if !slices.Contains(result, name) { t.Fatalf("Missing name %q in %v", name, result) } } } func TestFieldsListAsMap(t *testing.T) { f1 := &core.TextField{Name: "test1"} f2 := &core.EmailField{Name: "test2"} testFieldsList := core.NewFieldsList(f1, f2) result := testFieldsList.AsMap() expectedIndexes := []string{f1.Name, f2.Name} if len(result) != len(expectedIndexes) { t.Fatalf("Expected %d map elements, got %d\n%v", len(expectedIndexes), len(result), result) } for _, index := range expectedIndexes { if _, ok := result[index]; !ok { t.Fatalf("Missing index %q", index) } } } func TestFieldsListGetById(t *testing.T) { f1 := &core.TextField{Id: "id1", Name: "test1"} f2 := &core.EmailField{Id: "id2", Name: "test2"} testFieldsList := core.NewFieldsList(f1, f2) // missing field id result1 := testFieldsList.GetById("test1") if result1 != nil { t.Fatalf("Found unexpected field %v", result1) } // existing field id result2 := testFieldsList.GetById("id2") if result2 == nil || result2.GetId() != "id2" { t.Fatalf("Cannot find field with id %q, got %v ", "id2", result2) } } func TestFieldsListGetByName(t *testing.T) { f1 := &core.TextField{Id: "id1", Name: "test1"} f2 := &core.EmailField{Id: "id2", Name: "test2"} testFieldsList := core.NewFieldsList(f1, f2) // missing field name result1 := testFieldsList.GetByName("id1") if result1 != nil { t.Fatalf("Found unexpected field %v", result1) } // existing field name result2 := testFieldsList.GetByName("test2") if result2 == nil || result2.GetName() != "test2" { t.Fatalf("Cannot find field with name %q, got %v ", "test2", result2) } } func TestFieldsListRemove(t *testing.T) { testFieldsList := core.NewFieldsList( &core.TextField{Id: "id1", Name: "test1"}, &core.TextField{Id: "id2", Name: "test2"}, &core.TextField{Id: "id3", Name: "test3"}, &core.TextField{Id: "id4", Name: "test4"}, &core.TextField{Id: "id5", Name: "test5"}, &core.TextField{Id: "id6", Name: "test6"}, ) // remove by id testFieldsList.RemoveById("id2") testFieldsList.RemoveById("test3") // should do nothing // remove by name testFieldsList.RemoveByName("test5") testFieldsList.RemoveByName("id6") // should do nothing expected := []string{"test1", "test3", "test4", "test6"} if len(testFieldsList) != len(expected) { t.Fatalf("Expected %d, got %d\n%v", len(expected), len(testFieldsList), testFieldsList) } for _, name := range expected { if f := testFieldsList.GetByName(name); f == nil { t.Fatalf("Missing field %q", name) } } } func TestFieldsListAdd(t *testing.T) { f0 := &core.TextField{} f1 := &core.TextField{Name: "test1"} f2 := &core.TextField{Id: "f2Id", Name: "test2"} f3 := &core.TextField{Id: "f3Id", Name: "test3"} testFieldsList := core.NewFieldsList(f0, f1, f2, f3) f2New := &core.EmailField{Id: "f2Id", Name: "test2_new"} f4 := &core.URLField{Name: "test4"} testFieldsList.Add(f2New) testFieldsList.Add(f4) if len(testFieldsList) != 5 { t.Fatalf("Expected %d, got %d\n%v", 5, len(testFieldsList), testFieldsList) } // check if each field has id for _, f := range testFieldsList { if f.GetId() == "" { t.Fatalf("Expected field id to be set, found empty id for field %v", f) } } // check if f2 field was replaced if f := testFieldsList.GetById("f2Id"); f == nil || f.Type() != core.FieldTypeEmail { t.Fatalf("Expected f2 field to be replaced, found %v", f) } // check if f4 was added if f := testFieldsList.GetByName("test4"); f == nil || f.GetName() != "test4" { t.Fatalf("Expected f4 field to be added, found %v", f) } } func TestFieldsListAddMarshaledJSON(t *testing.T) { t.Parallel() scenarios := []struct { name string raw []byte expectError bool expectedFields map[string]string }{ { "nil", nil, false, map[string]string{"abc": core.FieldTypeNumber}, }, { "empty array", []byte(`[]`), false, map[string]string{"abc": core.FieldTypeNumber}, }, { "empty object", []byte(`{}`), true, map[string]string{"abc": core.FieldTypeNumber}, }, { "array with empty object", []byte(`[{}]`), true, map[string]string{"abc": core.FieldTypeNumber}, }, { "single object with invalid type", []byte(`{"type":"missing","name":"test"}`), true, map[string]string{"abc": core.FieldTypeNumber}, }, { "single object with valid type", []byte(`{"type":"text","name":"test"}`), false, map[string]string{ "abc": core.FieldTypeNumber, "test": core.FieldTypeText, }, }, { "array of object with valid types", []byte(`[{"type":"text","name":"test1"},{"type":"url","name":"test2"}]`), false, map[string]string{ "abc": core.FieldTypeNumber, "test1": core.FieldTypeText, "test2": core.FieldTypeURL, }, }, { "fields with duplicated ids should replace existing fields", []byte(`[{"type":"text","name":"test1"},{"type":"url","name":"test2"},{"type":"text","name":"abc2", "id":"abc_id"}]`), false, map[string]string{ "abc2": core.FieldTypeText, "test1": core.FieldTypeText, "test2": core.FieldTypeURL, }, }, } for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { testList := core.NewFieldsList(&core.NumberField{Name: "abc", Id: "abc_id"}) err := testList.AddMarshaledJSON(s.raw) hasErr := err != nil if hasErr != s.expectError { t.Fatalf("Expected hasErr %v, got %v", s.expectError, hasErr) } if len(s.expectedFields) != len(testList) { t.Fatalf("Expected %d fields, got %d", len(s.expectedFields), len(testList)) } for fieldName, typ := range s.expectedFields { f := testList.GetByName(fieldName) if f == nil { t.Errorf("Missing expected field %q", fieldName) continue } if f.Type() != typ { t.Errorf("Expect field %q to has type %q, got %q", fieldName, typ, f.Type()) } } }) } } func TestFieldsListStringAndValue(t *testing.T) { t.Run("empty list", func(t *testing.T) { testFieldsList := core.NewFieldsList() str := testFieldsList.String() if str != "[]" { t.Fatalf("Expected empty slice, got\n%q", str) } v, err := testFieldsList.Value() if err != nil { t.Fatal(err) } if v != str { t.Fatalf("Expected String and Value to match") } }) t.Run("list with fields", func(t *testing.T) { testFieldsList := core.NewFieldsList( &core.TextField{Id: "f1id", Name: "test1"}, &core.BoolField{Id: "f2id", Name: "test2"}, &core.URLField{Id: "f3id", Name: "test3"}, ) str := testFieldsList.String() v, err := testFieldsList.Value() if err != nil { t.Fatal(err) } if v != str { t.Fatalf("Expected String and Value to match") } expectedParts := []string{ `"type":"bool"`, `"type":"url"`, `"type":"text"`, `"id":"f1id"`, `"id":"f2id"`, `"id":"f3id"`, `"name":"test1"`, `"name":"test2"`, `"name":"test3"`, } for _, part := range expectedParts { if !strings.Contains(str, part) { t.Fatalf("Missing %q in\nn%v", part, str) } } }) } func TestFieldsListScan(t *testing.T) { scenarios := []struct { name string data any expectError bool expectJSON string }{ {"nil", nil, false, "[]"}, {"empty string", "", false, "[]"}, {"empty byte", []byte{}, false, "[]"}, {"empty string array", "[]", false, "[]"}, {"invalid string", "invalid", true, "[]"}, {"non-string", 123, true, "[]"}, {"item with no field type", `[{}]`, true, "[]"}, { "unknown field type", `[{"id":"123","name":"test1","type":"unknown"},{"id":"456","name":"test2","type":"bool"}]`, true, `[]`, }, { "only the minimum field options", `[{"id":"123","name":"test1","type":"text","required":true},{"id":"456","name":"test2","type":"bool"}]`, false, `[{"autogeneratePattern":"","hidden":false,"id":"123","max":0,"min":0,"name":"test1","pattern":"","presentable":false,"primaryKey":false,"required":true,"system":false,"type":"text"},{"hidden":false,"id":"456","name":"test2","presentable":false,"required":false,"system":false,"type":"bool"}]`, }, { "all field options", `[{"autogeneratePattern":"","hidden":true,"id":"123","max":12,"min":0,"name":"test1","pattern":"","presentable":true,"primaryKey":false,"required":true,"system":false,"type":"text"},{"hidden":false,"id":"456","name":"test2","presentable":false,"required":false,"system":true,"type":"bool"}]`, false, `[{"autogeneratePattern":"","hidden":true,"id":"123","max":12,"min":0,"name":"test1","pattern":"","presentable":true,"primaryKey":false,"required":true,"system":false,"type":"text"},{"hidden":false,"id":"456","name":"test2","presentable":false,"required":false,"system":true,"type":"bool"}]`, }, } for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { testFieldsList := core.FieldsList{} err := testFieldsList.Scan(s.data) hasErr := err != nil if hasErr != s.expectError { t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err) } str := testFieldsList.String() if str != s.expectJSON { t.Fatalf("Expected\n%v\ngot\n%v", s.expectJSON, str) } }) } } func TestFieldsListJSON(t *testing.T) { scenarios := []struct { name string data string expectError bool expectJSON string }{ {"empty string", "", true, "[]"}, {"invalid string", "invalid", true, "[]"}, {"empty string array", "[]", false, "[]"}, {"item with no field type", `[{}]`, true, "[]"}, { "unknown field type", `[{"id":"123","name":"test1","type":"unknown"},{"id":"456","name":"test2","type":"bool"}]`, true, `[]`, }, { "only the minimum field options", `[{"id":"123","name":"test1","type":"text","required":true},{"id":"456","name":"test2","type":"bool"}]`, false, `[{"autogeneratePattern":"","hidden":false,"id":"123","max":0,"min":0,"name":"test1","pattern":"","presentable":false,"primaryKey":false,"required":true,"system":false,"type":"text"},{"hidden":false,"id":"456","name":"test2","presentable":false,"required":false,"system":false,"type":"bool"}]`, }, { "all field options", `[{"autogeneratePattern":"","hidden":true,"id":"123","max":12,"min":0,"name":"test1","pattern":"","presentable":true,"primaryKey":false,"required":true,"system":false,"type":"text"},{"hidden":false,"id":"456","name":"test2","presentable":false,"required":false,"system":true,"type":"bool"}]`, false, `[{"autogeneratePattern":"","hidden":true,"id":"123","max":12,"min":0,"name":"test1","pattern":"","presentable":true,"primaryKey":false,"required":true,"system":false,"type":"text"},{"hidden":false,"id":"456","name":"test2","presentable":false,"required":false,"system":true,"type":"bool"}]`, }, } for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { testFieldsList := core.FieldsList{} err := testFieldsList.UnmarshalJSON([]byte(s.data)) hasErr := err != nil if hasErr != s.expectError { t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err) } raw, err := testFieldsList.MarshalJSON() if err != nil { t.Fatal(err) } str := string(raw) if str != s.expectJSON { t.Fatalf("Expected\n%v\ngot\n%v", s.expectJSON, str) } }) } }