| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | package core_test | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"testing" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/core" | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/tests" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestTextFieldBaseMethods(t *testing.T) { | 
					
						
							|  |  |  | 	testFieldBaseMethods(t, core.FieldTypeText) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestTextFieldColumnType(t *testing.T) { | 
					
						
							|  |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f := &core.TextField{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	expected := "TEXT DEFAULT '' NOT NULL" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if v := f.ColumnType(app); v != expected { | 
					
						
							|  |  |  | 		t.Fatalf("Expected\n%q\ngot\n%q", expected, v) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestTextFieldPrepareValue(t *testing.T) { | 
					
						
							|  |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f := &core.TextField{} | 
					
						
							|  |  |  | 	record := core.NewRecord(core.NewBaseCollection("test")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	scenarios := []struct { | 
					
						
							|  |  |  | 		raw      any | 
					
						
							|  |  |  | 		expected string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{"", ""}, | 
					
						
							|  |  |  | 		{"test", "test"}, | 
					
						
							|  |  |  | 		{false, "false"}, | 
					
						
							|  |  |  | 		{true, "true"}, | 
					
						
							|  |  |  | 		{123.456, "123.456"}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, s := range scenarios { | 
					
						
							|  |  |  | 		t.Run(fmt.Sprintf("%d_%#v", i, s.raw), func(t *testing.T) { | 
					
						
							|  |  |  | 			v, err := f.PrepareValue(record, s.raw) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			vStr, ok := v.(string) | 
					
						
							|  |  |  | 			if !ok { | 
					
						
							|  |  |  | 				t.Fatalf("Expected string instance, got %T", v) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if vStr != s.expected { | 
					
						
							|  |  |  | 				t.Fatalf("Expected %q, got %q", s.expected, v) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestTextFieldValidateValue(t *testing.T) { | 
					
						
							|  |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-19 21:59:59 +08:00
										 |  |  | 	collection, err := app.FindCollectionByNameOrId("demo1") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	existingRecord, err := app.FindFirstRecordByFilter(collection, "id != ''") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	scenarios := []struct { | 
					
						
							|  |  |  | 		name        string | 
					
						
							|  |  |  | 		field       *core.TextField | 
					
						
							|  |  |  | 		record      func() *core.Record | 
					
						
							|  |  |  | 		expectError bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"invalid raw value", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test"}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", 123) | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"zero field value (not required)", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", Pattern: `\d+`, Min: 10, Max: 100}, // other fields validators should be ignored
 | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"zero field value (required)", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", Required: true}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"non-zero field value (required)", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", Required: true}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "abc") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-11-19 21:59:59 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			"special forbidden character / (non-primaryKey)", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", PrimaryKey: false}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "/") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"special forbidden character \\ (non-primaryKey)", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", PrimaryKey: false}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "\\") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"special forbidden character / (primaryKey)", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", PrimaryKey: true}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "/") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"special forbidden character \\ (primaryKey)", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", PrimaryKey: true}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "\\") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			"zero field value (primaryKey)", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", PrimaryKey: true}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"non-zero field value (primaryKey)", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", PrimaryKey: true}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							| 
									
										
										
										
											2024-11-19 21:59:59 +08:00
										 |  |  | 				record.SetRaw("test", "abcd") | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-11-19 21:59:59 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			"case-insensitive duplicated primary key check", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", PrimaryKey: true}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", strings.ToUpper(existingRecord.Id)) | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			"< min", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", Min: 4}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "абв") // multi-byte
 | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			">= min", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", Min: 3}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "абв") // multi-byte
 | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"> default max", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test"}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", strings.Repeat("a", 5001)) | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"<= default max", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test"}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", strings.Repeat("a", 500)) | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"> max", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", Max: 2}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "абв") // multi-byte
 | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"<= max", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", Min: 3}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "абв") // multi-byte
 | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"mismatched pattern", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", Pattern: `\d+`}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "abc") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"matched pattern", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", Pattern: `\d+`}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "123") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, s := range scenarios { | 
					
						
							|  |  |  | 		t.Run(s.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			err := s.field.ValidateValue(context.Background(), app, s.record()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			hasErr := err != nil | 
					
						
							|  |  |  | 			if hasErr != s.expectError { | 
					
						
							|  |  |  | 				t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestTextFieldValidateSettings(t *testing.T) { | 
					
						
							|  |  |  | 	testDefaultFieldIdValidation(t, core.FieldTypeText) | 
					
						
							|  |  |  | 	testDefaultFieldNameValidation(t, core.FieldTypeText) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	scenarios := []struct { | 
					
						
							|  |  |  | 		name         string | 
					
						
							|  |  |  | 		field        func() *core.TextField | 
					
						
							|  |  |  | 		expectErrors []string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"zero minimal", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:   "test", | 
					
						
							|  |  |  | 					Name: "test", | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"primaryKey without required", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:         "test", | 
					
						
							|  |  |  | 					Name:       "id", | 
					
						
							|  |  |  | 					PrimaryKey: true, | 
					
						
							| 
									
										
										
										
											2024-11-19 23:21:25 +08:00
										 |  |  | 					Pattern:    `\d+`, | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{"required"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-11-19 23:21:25 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			"primaryKey without pattern", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:         "test", | 
					
						
							|  |  |  | 					Name:       "id", | 
					
						
							|  |  |  | 					PrimaryKey: true, | 
					
						
							|  |  |  | 					Required:   true, | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{"pattern"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			"primaryKey with hidden", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:         "test", | 
					
						
							|  |  |  | 					Name:       "id", | 
					
						
							|  |  |  | 					Required:   true, | 
					
						
							|  |  |  | 					PrimaryKey: true, | 
					
						
							|  |  |  | 					Hidden:     true, | 
					
						
							| 
									
										
										
										
											2024-11-19 23:21:25 +08:00
										 |  |  | 					Pattern:    `\d+`, | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{"hidden"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"primaryKey with name != id", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:         "test", | 
					
						
							|  |  |  | 					Name:       "test", | 
					
						
							|  |  |  | 					PrimaryKey: true, | 
					
						
							|  |  |  | 					Required:   true, | 
					
						
							| 
									
										
										
										
											2024-11-19 23:21:25 +08:00
										 |  |  | 					Pattern:    `\d+`, | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{"name"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"multiple primaryKey fields", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:         "test2", | 
					
						
							|  |  |  | 					Name:       "id", | 
					
						
							|  |  |  | 					PrimaryKey: true, | 
					
						
							| 
									
										
										
										
											2024-11-19 23:21:25 +08:00
										 |  |  | 					Pattern:    `\d+`, | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 					Required:   true, | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{"primaryKey"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"invalid pattern", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:      "test2", | 
					
						
							|  |  |  | 					Name:    "id", | 
					
						
							|  |  |  | 					Pattern: `(invalid`, | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{"pattern"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"valid pattern", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:      "test2", | 
					
						
							|  |  |  | 					Name:    "id", | 
					
						
							|  |  |  | 					Pattern: `\d+`, | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"invalid autogeneratePattern", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:                  "test2", | 
					
						
							|  |  |  | 					Name:                "id", | 
					
						
							|  |  |  | 					AutogeneratePattern: `(invalid`, | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{"autogeneratePattern"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"valid autogeneratePattern", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:                  "test2", | 
					
						
							|  |  |  | 					Name:                "id", | 
					
						
							|  |  |  | 					AutogeneratePattern: `[a-z]+`, | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"conflicting pattern and autogeneratePattern", | 
					
						
							|  |  |  | 			func() *core.TextField { | 
					
						
							|  |  |  | 				return &core.TextField{ | 
					
						
							|  |  |  | 					Id:                  "test2", | 
					
						
							|  |  |  | 					Name:                "id", | 
					
						
							|  |  |  | 					Pattern:             `\d+`, | 
					
						
							|  |  |  | 					AutogeneratePattern: `[a-z]+`, | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			[]string{"autogeneratePattern"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, s := range scenarios { | 
					
						
							|  |  |  | 		t.Run(s.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			field := s.field() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			collection := core.NewBaseCollection("test_collection") | 
					
						
							|  |  |  | 			collection.Fields.GetByName("id").SetId("test") // set a dummy known id so that it can be replaced
 | 
					
						
							|  |  |  | 			collection.Fields.Add(field) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			errs := field.ValidateSettings(context.Background(), app, collection) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			tests.TestValidationErrors(t, errs, s.expectErrors) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestTextFieldAutogenerate(t *testing.T) { | 
					
						
							|  |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	collection := core.NewBaseCollection("test_collection") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	scenarios := []struct { | 
					
						
							|  |  |  | 		name       string | 
					
						
							|  |  |  | 		actionName string | 
					
						
							|  |  |  | 		field      *core.TextField | 
					
						
							|  |  |  | 		record     func() *core.Record | 
					
						
							|  |  |  | 		expected   string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"non-matching action", | 
					
						
							|  |  |  | 			core.InterceptorActionUpdate, | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", AutogeneratePattern: "abc"}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				return core.NewRecord(collection) | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"matching action (create)", | 
					
						
							|  |  |  | 			core.InterceptorActionCreate, | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", AutogeneratePattern: "abc"}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				return core.NewRecord(collection) | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"abc", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"matching action (validate)", | 
					
						
							|  |  |  | 			core.InterceptorActionValidate, | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", AutogeneratePattern: "abc"}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				return core.NewRecord(collection) | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"abc", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"existing non-zero value", | 
					
						
							|  |  |  | 			core.InterceptorActionCreate, | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", AutogeneratePattern: "abc"}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.SetRaw("test", "123") | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"123", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"non-new record", | 
					
						
							|  |  |  | 			core.InterceptorActionValidate, | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", AutogeneratePattern: "abc"}, | 
					
						
							|  |  |  | 			func() *core.Record { | 
					
						
							|  |  |  | 				record := core.NewRecord(collection) | 
					
						
							|  |  |  | 				record.Id = "test" | 
					
						
							|  |  |  | 				record.PostScan() | 
					
						
							|  |  |  | 				return record | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, s := range scenarios { | 
					
						
							|  |  |  | 		t.Run(s.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			actionCalls := 0 | 
					
						
							|  |  |  | 			record := s.record() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			err := s.field.Intercept(context.Background(), app, record, s.actionName, func() error { | 
					
						
							|  |  |  | 				actionCalls++ | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if actionCalls != 1 { | 
					
						
							|  |  |  | 				t.Fatalf("Expected actionCalls %d, got %d", 1, actionCalls) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			v := record.GetString(s.field.GetName()) | 
					
						
							|  |  |  | 			if v != s.expected { | 
					
						
							|  |  |  | 				t.Fatalf("Expected value %q, got %q", s.expected, v) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestTextFieldFindSetter(t *testing.T) { | 
					
						
							|  |  |  | 	scenarios := []struct { | 
					
						
							|  |  |  | 		name      string | 
					
						
							|  |  |  | 		key       string | 
					
						
							|  |  |  | 		value     any | 
					
						
							|  |  |  | 		field     *core.TextField | 
					
						
							|  |  |  | 		hasSetter bool | 
					
						
							|  |  |  | 		expected  string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"no match", | 
					
						
							|  |  |  | 			"example", | 
					
						
							|  |  |  | 			"abc", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", AutogeneratePattern: "test"}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"exact match", | 
					
						
							|  |  |  | 			"test", | 
					
						
							|  |  |  | 			"abc", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", AutogeneratePattern: "test"}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			"abc", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"autogenerate modifier", | 
					
						
							|  |  |  | 			"test:autogenerate", | 
					
						
							|  |  |  | 			"abc", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test", AutogeneratePattern: "test"}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			"abctest", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"autogenerate modifier without AutogeneratePattern option", | 
					
						
							|  |  |  | 			"test:autogenerate", | 
					
						
							|  |  |  | 			"abc", | 
					
						
							|  |  |  | 			&core.TextField{Name: "test"}, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			"abc", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, s := range scenarios { | 
					
						
							|  |  |  | 		t.Run(s.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			collection := core.NewBaseCollection("test_collection") | 
					
						
							|  |  |  | 			collection.Fields.Add(s.field) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			setter := s.field.FindSetter(s.key) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			hasSetter := setter != nil | 
					
						
							|  |  |  | 			if hasSetter != s.hasSetter { | 
					
						
							|  |  |  | 				t.Fatalf("Expected hasSetter %v, got %v", s.hasSetter, hasSetter) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if !hasSetter { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			record := core.NewRecord(collection) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			setter(record, s.value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			result := record.GetString(s.field.Name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if result != s.expected { | 
					
						
							|  |  |  | 				t.Fatalf("Expected %q, got %q", s.expected, result) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |