1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2024-11-25 09:21:11 +02:00
pocketbase/models/record_test.go

1711 lines
46 KiB
Go

package models_test
import (
"database/sql"
"encoding/json"
"testing"
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/models/schema"
"github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/types"
)
func TestNewRecord(t *testing.T) {
collection := &models.Collection{
Name: "test_collection",
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "test",
Type: schema.FieldTypeText,
},
),
}
m := models.NewRecord(collection)
if m.Collection().Name != collection.Name {
t.Fatalf("Expected collection with name %q, got %q", collection.Id, m.Collection().Id)
}
if len(m.SchemaData()) != 0 {
t.Fatalf("Expected empty schema data, got %v", m.SchemaData())
}
}
func TestNewRecordFromNullStringMap(t *testing.T) {
collection := &models.Collection{
Name: "test",
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field2",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field3",
Type: schema.FieldTypeBool,
},
&schema.SchemaField{
Name: "field4",
Type: schema.FieldTypeNumber,
},
&schema.SchemaField{
Name: "field5",
Type: schema.FieldTypeSelect,
Options: &schema.SelectOptions{
Values: []string{"test1", "test2"},
MaxSelect: 1,
},
},
&schema.SchemaField{
Name: "field6",
Type: schema.FieldTypeFile,
Options: &schema.FileOptions{
MaxSelect: 2,
MaxSize: 1,
},
},
),
}
data := dbx.NullStringMap{
"id": sql.NullString{
String: "test_id",
Valid: true,
},
"created": sql.NullString{
String: "2022-01-01 10:00:00.123Z",
Valid: true,
},
"updated": sql.NullString{
String: "2022-01-01 10:00:00.456Z",
Valid: true,
},
// auth collection specific fields
"username": sql.NullString{
String: "test_username",
Valid: true,
},
"email": sql.NullString{
String: "test_email",
Valid: true,
},
"emailVisibility": sql.NullString{
String: "true",
Valid: true,
},
"verified": sql.NullString{
String: "",
Valid: false,
},
"tokenKey": sql.NullString{
String: "test_tokenKey",
Valid: true,
},
"passwordHash": sql.NullString{
String: "test_passwordHash",
Valid: true,
},
"lastResetSentAt": sql.NullString{
String: "2022-01-02 10:00:00.123Z",
Valid: true,
},
"lastVerificationSentAt": sql.NullString{
String: "2022-02-03 10:00:00.456Z",
Valid: true,
},
// custom schema fields
"field1": sql.NullString{
String: "test",
Valid: true,
},
"field2": sql.NullString{
String: "test",
Valid: false, // test invalid db serialization
},
"field3": sql.NullString{
String: "true",
Valid: true,
},
"field4": sql.NullString{
String: "123.123",
Valid: true,
},
"field5": sql.NullString{
String: `["test1","test2"]`, // will select only the first elem
Valid: true,
},
"field6": sql.NullString{
String: "test", // will be converted to slice
Valid: true,
},
"unknown": sql.NullString{
String: "test",
Valid: true,
},
}
scenarios := []struct {
collectionType string
expectedJson string
}{
{
models.CollectionTypeBase,
`{"collectionId":"","collectionName":"test","created":"2022-01-01 10:00:00.123Z","field1":"test","field2":"","field3":true,"field4":123.123,"field5":"test1","field6":["test"],"id":"test_id","updated":"2022-01-01 10:00:00.456Z"}`,
},
{
models.CollectionTypeAuth,
`{"collectionId":"","collectionName":"test","created":"2022-01-01 10:00:00.123Z","email":"test_email","emailVisibility":true,"field1":"test","field2":"","field3":true,"field4":123.123,"field5":"test1","field6":["test"],"id":"test_id","updated":"2022-01-01 10:00:00.456Z","username":"test_username","verified":false}`,
},
}
for i, s := range scenarios {
collection.Type = s.collectionType
m := models.NewRecordFromNullStringMap(collection, data)
m.IgnoreEmailVisibility(true)
encoded, err := m.MarshalJSON()
if err != nil {
t.Errorf("(%d) Unexpected error: %v", i, err)
continue
}
if string(encoded) != s.expectedJson {
t.Errorf("(%d) Expected \n%v \ngot \n%v", i, s.expectedJson, string(encoded))
}
// additional data checks
if collection.IsAuth() {
if v := m.GetString(schema.FieldNamePasswordHash); v != "test_passwordHash" {
t.Errorf("(%d) Expected %q, got %q", i, "test_passwordHash", v)
}
if v := m.GetString(schema.FieldNameTokenKey); v != "test_tokenKey" {
t.Errorf("(%d) Expected %q, got %q", i, "test_tokenKey", v)
}
if v := m.GetString(schema.FieldNameLastResetSentAt); v != "2022-01-02 10:00:00.123Z" {
t.Errorf("(%d) Expected %q, got %q", i, "2022-01-02 10:00:00.123Z", v)
}
if v := m.GetString(schema.FieldNameLastVerificationSentAt); v != "2022-02-03 10:00:00.456Z" {
t.Errorf("(%d) Expected %q, got %q", i, "2022-01-02 10:00:00.123Z", v)
}
}
}
}
func TestNewRecordsFromNullStringMaps(t *testing.T) {
collection := &models.Collection{
Name: "test",
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field2",
Type: schema.FieldTypeNumber,
},
&schema.SchemaField{
Name: "field3",
Type: schema.FieldTypeUrl,
},
),
}
data := []dbx.NullStringMap{
{
"id": sql.NullString{
String: "test_id1",
Valid: true,
},
"created": sql.NullString{
String: "2022-01-01 10:00:00.123Z",
Valid: true,
},
"updated": sql.NullString{
String: "2022-01-01 10:00:00.456Z",
Valid: true,
},
// partial auth fields
"email": sql.NullString{
String: "test_email",
Valid: true,
},
"tokenKey": sql.NullString{
String: "test_tokenKey",
Valid: true,
},
"emailVisibility": sql.NullString{
String: "true",
Valid: true,
},
// custom schema fields
"field1": sql.NullString{
String: "test",
Valid: true,
},
"field2": sql.NullString{
String: "123.123",
Valid: true,
},
"field3": sql.NullString{
String: "test",
Valid: false, // should force resolving to empty string
},
"unknown": sql.NullString{
String: "test",
Valid: true,
},
},
{
"field3": sql.NullString{
String: "test",
Valid: true,
},
"email": sql.NullString{
String: "test_email",
Valid: true,
},
"emailVisibility": sql.NullString{
String: "false",
Valid: true,
},
},
}
scenarios := []struct {
collectionType string
expectedJson string
}{
{
models.CollectionTypeBase,
`[{"collectionId":"","collectionName":"test","created":"2022-01-01 10:00:00.123Z","field1":"test","field2":123.123,"field3":"","id":"test_id1","updated":"2022-01-01 10:00:00.456Z"},{"collectionId":"","collectionName":"test","created":"","field1":"","field2":0,"field3":"test","id":"","updated":""}]`,
},
{
models.CollectionTypeAuth,
`[{"collectionId":"","collectionName":"test","created":"2022-01-01 10:00:00.123Z","email":"test_email","emailVisibility":true,"field1":"test","field2":123.123,"field3":"","id":"test_id1","updated":"2022-01-01 10:00:00.456Z","username":"","verified":false},{"collectionId":"","collectionName":"test","created":"","emailVisibility":false,"field1":"","field2":0,"field3":"test","id":"","updated":"","username":"","verified":false}]`,
},
}
for i, s := range scenarios {
collection.Type = s.collectionType
result := models.NewRecordsFromNullStringMaps(collection, data)
encoded, err := json.Marshal(result)
if err != nil {
t.Errorf("(%d) Unexpected error: %v", i, err)
continue
}
if string(encoded) != s.expectedJson {
t.Errorf("(%d) Expected \n%v \ngot \n%v", i, s.expectedJson, string(encoded))
}
}
}
func TestRecordTableName(t *testing.T) {
collection := &models.Collection{}
collection.Name = "test"
collection.RefreshId()
m := models.NewRecord(collection)
if m.TableName() != collection.Name {
t.Fatalf("Expected table %q, got %q", collection.Name, m.TableName())
}
}
func TestRecordCollection(t *testing.T) {
collection := &models.Collection{}
collection.RefreshId()
m := models.NewRecord(collection)
if m.Collection().Id != collection.Id {
t.Fatalf("Expected collection with id %v, got %v", collection.Id, m.Collection().Id)
}
}
func TestRecordOriginalCopy(t *testing.T) {
m := models.NewRecord(&models.Collection{})
m.Load(map[string]any{"f": "123"})
// change the field
m.Set("f", "456")
if v := m.GetString("f"); v != "456" {
t.Fatalf("Expected f to be %q, got %q", "456", v)
}
if v := m.OriginalCopy().GetString("f"); v != "123" {
t.Fatalf("Expected the initial/original f to be %q, got %q", "123", v)
}
// Loading new data shouldn't affect the original state
m.Load(map[string]any{"f": "789"})
if v := m.GetString("f"); v != "789" {
t.Fatalf("Expected f to be %q, got %q", "789", v)
}
if v := m.OriginalCopy().GetString("f"); v != "123" {
t.Fatalf("Expected the initial/original f still to be %q, got %q", "123", v)
}
}
func TestRecordExpand(t *testing.T) {
collection := &models.Collection{}
m := models.NewRecord(collection)
data := map[string]any{"test": 123}
m.SetExpand(data)
// change the original data to check if it was shallow copied
data["test"] = 456
expand := m.Expand()
if v, ok := expand["test"]; !ok || v != 123 {
t.Fatalf("Expected expand.test to be %v, got %v", 123, v)
}
}
func TestRecordSchemaData(t *testing.T) {
collection := &models.Collection{
Type: models.CollectionTypeAuth,
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field2",
Type: schema.FieldTypeNumber,
},
),
}
m := models.NewRecord(collection)
m.Set("email", "test@example.com")
m.Set("field1", 123)
m.Set("field2", 456)
m.Set("unknown", 789)
encoded, err := json.Marshal(m.SchemaData())
if err != nil {
t.Fatal(err)
}
expected := `{"field1":"123","field2":456}`
if v := string(encoded); v != expected {
t.Fatalf("Expected \n%v \ngot \n%v", v, expected)
}
}
func TestRecordUnknownData(t *testing.T) {
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field2",
Type: schema.FieldTypeNumber,
},
),
}
data := map[string]any{
"id": "test_id",
"created": "2022-01-01 00:00:00.000",
"updated": "2022-01-01 00:00:00.000",
"collectionId": "test_collectionId",
"collectionName": "test_collectionName",
"expand": "test_expand",
"field1": "test_field1",
"field2": "test_field1",
"unknown1": "test_unknown1",
"unknown2": "test_unknown2",
"passwordHash": "test_passwordHash",
"username": "test_username",
"emailVisibility": true,
"email": "test_email",
"verified": true,
"tokenKey": "test_tokenKey",
"lastResetSentAt": "2022-01-01 00:00:00.000",
"lastVerificationSentAt": "2022-01-01 00:00:00.000",
}
scenarios := []struct {
collectionType string
expectedKeys []string
}{
{
models.CollectionTypeBase,
[]string{
"unknown1",
"unknown2",
"passwordHash",
"username",
"emailVisibility",
"email",
"verified",
"tokenKey",
"lastResetSentAt",
"lastVerificationSentAt",
},
},
{
models.CollectionTypeAuth,
[]string{"unknown1", "unknown2"},
},
}
for i, s := range scenarios {
collection.Type = s.collectionType
m := models.NewRecord(collection)
m.Load(data)
result := m.UnknownData()
if len(result) != len(s.expectedKeys) {
t.Errorf("(%d) Expected data \n%v \ngot \n%v", i, s.expectedKeys, result)
continue
}
for _, key := range s.expectedKeys {
if _, ok := result[key]; !ok {
t.Errorf("(%d) Missing expected key %q in \n%v", i, key, result)
}
}
}
}
func TestRecordSetAndGet(t *testing.T) {
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field2",
Type: schema.FieldTypeNumber,
},
),
}
m := models.NewRecord(collection)
m.Set("id", "test_id")
m.Set("created", "2022-09-15 00:00:00.123Z")
m.Set("updated", "invalid")
m.Set("field1", 123) // should be casted to string
m.Set("field2", "invlaid") // should be casted to zero-number
m.Set("unknown", 456) // undefined fields are allowed but not exported by default
m.Set("expand", map[string]any{"test": 123}) // should store the value in m.expand
if m.Get("id") != "test_id" {
t.Fatalf("Expected id %q, got %q", "test_id", m.Get("id"))
}
if m.GetString("created") != "2022-09-15 00:00:00.123Z" {
t.Fatalf("Expected created %q, got %q", "2022-09-15 00:00:00.123Z", m.GetString("created"))
}
if m.GetString("updated") != "" {
t.Fatalf("Expected updated to be empty, got %q", m.GetString("updated"))
}
if m.Get("field1") != "123" {
t.Fatalf("Expected field1 %q, got %v", "123", m.Get("field1"))
}
if m.Get("field2") != 0.0 {
t.Fatalf("Expected field2 %v, got %v", 0.0, m.Get("field2"))
}
if m.Get("unknown") != 456 {
t.Fatalf("Expected unknown %v, got %v", 456, m.Get("unknown"))
}
if m.Expand()["test"] != 123 {
t.Fatalf("Expected expand to be %v, got %v", map[string]any{"test": 123}, m.Expand())
}
}
func TestRecordGetBool(t *testing.T) {
scenarios := []struct {
value any
expected bool
}{
{nil, false},
{"", false},
{0, false},
{1, true},
{[]string{"true"}, false},
{time.Now(), false},
{"test", false},
{"false", false},
{"true", true},
{false, false},
{true, true},
}
collection := &models.Collection{}
for i, s := range scenarios {
m := models.NewRecord(collection)
m.Set("test", s.value)
result := m.GetBool("test")
if result != s.expected {
t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
}
}
}
func TestRecordGetString(t *testing.T) {
scenarios := []struct {
value any
expected string
}{
{nil, ""},
{"", ""},
{0, "0"},
{1.4, "1.4"},
{[]string{"true"}, ""},
{map[string]int{"test": 1}, ""},
{[]byte("abc"), "abc"},
{"test", "test"},
{false, "false"},
{true, "true"},
}
collection := &models.Collection{}
for i, s := range scenarios {
m := models.NewRecord(collection)
m.Set("test", s.value)
result := m.GetString("test")
if result != s.expected {
t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
}
}
}
func TestRecordGetInt(t *testing.T) {
scenarios := []struct {
value any
expected int
}{
{nil, 0},
{"", 0},
{[]string{"true"}, 0},
{map[string]int{"test": 1}, 0},
{time.Now(), 0},
{"test", 0},
{123, 123},
{2.4, 2},
{"123", 123},
{"123.5", 0},
{false, 0},
{true, 1},
}
collection := &models.Collection{}
for i, s := range scenarios {
m := models.NewRecord(collection)
m.Set("test", s.value)
result := m.GetInt("test")
if result != s.expected {
t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
}
}
}
func TestRecordGetFloat(t *testing.T) {
scenarios := []struct {
value any
expected float64
}{
{nil, 0},
{"", 0},
{[]string{"true"}, 0},
{map[string]int{"test": 1}, 0},
{time.Now(), 0},
{"test", 0},
{123, 123},
{2.4, 2.4},
{"123", 123},
{"123.5", 123.5},
{false, 0},
{true, 1},
}
collection := &models.Collection{}
for i, s := range scenarios {
m := models.NewRecord(collection)
m.Set("test", s.value)
result := m.GetFloat("test")
if result != s.expected {
t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
}
}
}
func TestRecordGetTime(t *testing.T) {
nowTime := time.Now()
testTime, _ := time.Parse(types.DefaultDateLayout, "2022-01-01 08:00:40.000Z")
scenarios := []struct {
value any
expected time.Time
}{
{nil, time.Time{}},
{"", time.Time{}},
{false, time.Time{}},
{true, time.Time{}},
{"test", time.Time{}},
{[]string{"true"}, time.Time{}},
{map[string]int{"test": 1}, time.Time{}},
{1641024040, testTime},
{"2022-01-01 08:00:40.000", testTime},
{nowTime, nowTime},
}
collection := &models.Collection{}
for i, s := range scenarios {
m := models.NewRecord(collection)
m.Set("test", s.value)
result := m.GetTime("test")
if !result.Equal(s.expected) {
t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
}
}
}
func TestRecordGetDateTime(t *testing.T) {
nowTime := time.Now()
testTime, _ := time.Parse(types.DefaultDateLayout, "2022-01-01 08:00:40.000Z")
scenarios := []struct {
value any
expected time.Time
}{
{nil, time.Time{}},
{"", time.Time{}},
{false, time.Time{}},
{true, time.Time{}},
{"test", time.Time{}},
{[]string{"true"}, time.Time{}},
{map[string]int{"test": 1}, time.Time{}},
{1641024040, testTime},
{"2022-01-01 08:00:40.000", testTime},
{nowTime, nowTime},
}
collection := &models.Collection{}
for i, s := range scenarios {
m := models.NewRecord(collection)
m.Set("test", s.value)
result := m.GetDateTime("test")
if !result.Time().Equal(s.expected) {
t.Errorf("(%d) Expected %v, got %v", i, s.expected, result)
}
}
}
func TestRecordGetStringSlice(t *testing.T) {
nowTime := time.Now()
scenarios := []struct {
value any
expected []string
}{
{nil, []string{}},
{"", []string{}},
{false, []string{"false"}},
{true, []string{"true"}},
{nowTime, []string{}},
{123, []string{"123"}},
{"test", []string{"test"}},
{map[string]int{"test": 1}, []string{}},
{`["test1", "test2"]`, []string{"test1", "test2"}},
{[]int{123, 123, 456}, []string{"123", "456"}},
{[]string{"test", "test", "123"}, []string{"test", "123"}},
}
collection := &models.Collection{}
for i, s := range scenarios {
m := models.NewRecord(collection)
m.Set("test", s.value)
result := m.GetStringSlice("test")
if len(result) != len(s.expected) {
t.Errorf("(%d) Expected %d elements, got %d: %v", i, len(s.expected), len(result), result)
continue
}
for _, v := range result {
if !list.ExistInSlice(v, s.expected) {
t.Errorf("(%d) Cannot find %v in %v", i, v, s.expected)
}
}
}
}
func TestRecordUnmarshalJSONField(t *testing.T) {
collection := &models.Collection{
Schema: schema.NewSchema(&schema.SchemaField{
Name: "field",
Type: schema.FieldTypeJson,
}),
}
m := models.NewRecord(collection)
var testPointer *string
var testStr string
var testInt int
var testBool bool
var testSlice []int
var testMap map[string]any
scenarios := []struct {
value any
destination any
expectError bool
expectedJson string
}{
{nil, testStr, true, `""`},
{"", testStr, true, `""`},
{1, testInt, false, `1`},
{true, testBool, false, `true`},
{[]int{1, 2, 3}, testSlice, false, `[1,2,3]`},
{map[string]any{"test": 123}, testMap, false, `{"test":123}`},
// json encoded values
{`null`, testPointer, false, `null`},
{`true`, testBool, false, `true`},
{`456`, testInt, false, `456`},
{`"test"`, testStr, false, `"test"`},
{`[4,5,6]`, testSlice, false, `[4,5,6]`},
{`{"test":456}`, testMap, false, `{"test":456}`},
}
for i, s := range scenarios {
m.Set("field", s.value)
err := m.UnmarshalJSONField("field", &s.destination)
hasErr := err != nil
if hasErr != s.expectError {
t.Errorf("(%d) Expected hasErr %v, got %v", i, s.expectError, hasErr)
continue
}
raw, _ := json.Marshal(s.destination)
if v := string(raw); v != s.expectedJson {
t.Errorf("(%d) Expected %q, got %q", i, s.expectedJson, v)
}
}
}
func TestRecordBaseFilesPath(t *testing.T) {
collection := &models.Collection{}
collection.RefreshId()
collection.Name = "test"
m := models.NewRecord(collection)
m.RefreshId()
expected := collection.BaseFilesPath() + "/" + m.Id
result := m.BaseFilesPath()
if result != expected {
t.Fatalf("Expected %q, got %q", expected, result)
}
}
func TestRecordFindFileFieldByFile(t *testing.T) {
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field2",
Type: schema.FieldTypeFile,
Options: &schema.FileOptions{
MaxSelect: 1,
MaxSize: 1,
},
},
&schema.SchemaField{
Name: "field3",
Type: schema.FieldTypeFile,
Options: &schema.FileOptions{
MaxSelect: 2,
MaxSize: 1,
},
},
),
}
m := models.NewRecord(collection)
m.Set("field1", "test")
m.Set("field2", "test.png")
m.Set("field3", []string{"test1.png", "test2.png"})
scenarios := []struct {
filename string
expectField string
}{
{"", ""},
{"test", ""},
{"test2", ""},
{"test.png", "field2"},
{"test2.png", "field3"},
}
for i, s := range scenarios {
result := m.FindFileFieldByFile(s.filename)
var fieldName string
if result != nil {
fieldName = result.Name
}
if s.expectField != fieldName {
t.Errorf("(%d) Expected field %v, got %v", i, s.expectField, result)
continue
}
}
}
func TestRecordLoadAndData(t *testing.T) {
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field2",
Type: schema.FieldTypeNumber,
},
),
}
data := map[string]any{
"id": "test_id",
"created": "2022-01-01 10:00:00.123Z",
"updated": "2022-01-01 10:00:00.456Z",
"field1": "test_field",
"field2": "123", // should be casted to float
"unknown": "test_unknown",
// auth collection sepcific casting test
"passwordHash": "test_passwordHash",
"emailVisibility": "12345", // should be casted to bool only for auth collections
"username": 123, // should be casted to string only for auth collections
"email": "test_email",
"verified": true,
"tokenKey": "test_tokenKey",
"lastResetSentAt": "2022-01-01 11:00:00.000", // should be casted to DateTime only for auth collections
"lastVerificationSentAt": "2022-01-01 12:00:00.000", // should be casted to DateTime only for auth collections
}
scenarios := []struct {
collectionType string
}{
{models.CollectionTypeBase},
{models.CollectionTypeAuth},
}
for i, s := range scenarios {
collection.Type = s.collectionType
m := models.NewRecord(collection)
m.Load(data)
expectations := map[string]any{}
for k, v := range data {
expectations[k] = v
}
expectations["created"], _ = types.ParseDateTime("2022-01-01 10:00:00.123Z")
expectations["updated"], _ = types.ParseDateTime("2022-01-01 10:00:00.456Z")
expectations["field2"] = 123.0
// extra casting test
if collection.IsAuth() {
lastResetSentAt, _ := types.ParseDateTime(expectations["lastResetSentAt"])
lastVerificationSentAt, _ := types.ParseDateTime(expectations["lastVerificationSentAt"])
expectations["emailVisibility"] = false
expectations["username"] = "123"
expectations["verified"] = true
expectations["lastResetSentAt"] = lastResetSentAt
expectations["lastVerificationSentAt"] = lastVerificationSentAt
}
for k, v := range expectations {
if m.Get(k) != v {
t.Errorf("(%d) Expected field %s to be %v, got %v", i, k, v, m.Get(k))
}
}
}
}
func TestRecordColumnValueMap(t *testing.T) {
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field2",
Type: schema.FieldTypeFile,
Options: &schema.FileOptions{
MaxSelect: 1,
MaxSize: 1,
},
},
&schema.SchemaField{
Name: "field3",
Type: schema.FieldTypeSelect,
Options: &schema.SelectOptions{
MaxSelect: 2,
Values: []string{"test1", "test2", "test3"},
},
},
&schema.SchemaField{
Name: "field4",
Type: schema.FieldTypeRelation,
Options: &schema.RelationOptions{
MaxSelect: types.Pointer(2),
},
},
),
}
scenarios := []struct {
collectionType string
expectedJson string
}{
{
models.CollectionTypeBase,
`{"created":"2022-01-01 10:00:30.123Z","field1":"test","field2":"test.png","field3":["test1","test2"],"field4":["test11","test12"],"id":"test_id","updated":""}`,
},
{
models.CollectionTypeAuth,
`{"created":"2022-01-01 10:00:30.123Z","email":"test_email","emailVisibility":true,"field1":"test","field2":"test.png","field3":["test1","test2"],"field4":["test11","test12"],"id":"test_id","lastResetSentAt":"2022-01-02 10:00:30.123Z","lastVerificationSentAt":"","passwordHash":"test_passwordHash","tokenKey":"test_tokenKey","updated":"","username":"test_username","verified":false}`,
},
}
created, _ := types.ParseDateTime("2022-01-01 10:00:30.123Z")
lastResetSentAt, _ := types.ParseDateTime("2022-01-02 10:00:30.123Z")
data := map[string]any{
"id": "test_id",
"created": created,
"field1": "test",
"field2": "test.png",
"field3": []string{"test1", "test2"},
"field4": []string{"test11", "test12", "test11"}, // strip duplicate,
"unknown": "test_unknown",
"passwordHash": "test_passwordHash",
"username": "test_username",
"emailVisibility": true,
"email": "test_email",
"verified": "invalid", // should be casted
"tokenKey": "test_tokenKey",
"lastResetSentAt": lastResetSentAt,
}
m := models.NewRecord(collection)
for i, s := range scenarios {
collection.Type = s.collectionType
m.Load(data)
result := m.ColumnValueMap()
encoded, err := json.Marshal(result)
if err != nil {
t.Errorf("(%d) Unexpected error %v", i, err)
continue
}
if str := string(encoded); str != s.expectedJson {
t.Errorf("(%d) Expected \n%v \ngot \n%v", i, s.expectedJson, str)
}
}
}
func TestRecordPublicExportAndMarshalJSON(t *testing.T) {
collection := &models.Collection{
Name: "c_name",
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field2",
Type: schema.FieldTypeFile,
Options: &schema.FileOptions{
MaxSelect: 1,
MaxSize: 1,
},
},
&schema.SchemaField{
Name: "field3",
Type: schema.FieldTypeSelect,
Options: &schema.SelectOptions{
MaxSelect: 2,
Values: []string{"test1", "test2", "test3"},
},
},
),
}
collection.Id = "c_id"
scenarios := []struct {
collectionType string
exportHidden bool
exportUnknown bool
expectedJson string
}{
// base
{
models.CollectionTypeBase,
false,
false,
`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","updated":""}`,
},
{
models.CollectionTypeBase,
true,
false,
`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","updated":""}`,
},
{
models.CollectionTypeBase,
false,
true,
`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","email":"test_email","emailVisibility":"test_invalid","expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","lastResetSentAt":"2022-01-02 10:00:30.123Z","lastVerificationSentAt":"test_lastVerificationSentAt","passwordHash":"test_passwordHash","tokenKey":"test_tokenKey","unknown":"test_unknown","updated":"","username":123,"verified":true}`,
},
{
models.CollectionTypeBase,
true,
true,
`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","email":"test_email","emailVisibility":"test_invalid","expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","lastResetSentAt":"2022-01-02 10:00:30.123Z","lastVerificationSentAt":"test_lastVerificationSentAt","passwordHash":"test_passwordHash","tokenKey":"test_tokenKey","unknown":"test_unknown","updated":"","username":123,"verified":true}`,
},
// auth
{
models.CollectionTypeAuth,
false,
false,
`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","emailVisibility":false,"expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","updated":"","username":"123","verified":true}`,
},
{
models.CollectionTypeAuth,
true,
false,
`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","email":"test_email","emailVisibility":false,"expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","updated":"","username":"123","verified":true}`,
},
{
models.CollectionTypeAuth,
false,
true,
`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","emailVisibility":false,"expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","unknown":"test_unknown","updated":"","username":"123","verified":true}`,
},
{
models.CollectionTypeAuth,
true,
true,
`{"collectionId":"c_id","collectionName":"c_name","created":"2022-01-01 10:00:30.123Z","email":"test_email","emailVisibility":false,"expand":{"test":123},"field1":"test","field2":"test.png","field3":["test1","test2"],"id":"test_id","unknown":"test_unknown","updated":"","username":"123","verified":true}`,
},
}
created, _ := types.ParseDateTime("2022-01-01 10:00:30.123Z")
lastResetSentAt, _ := types.ParseDateTime("2022-01-02 10:00:30.123Z")
data := map[string]any{
"id": "test_id",
"created": created,
"field1": "test",
"field2": "test.png",
"field3": []string{"test1", "test2"},
"expand": map[string]any{"test": 123},
"collectionId": "m_id", // should be always ignored
"collectionName": "m_name", // should be always ignored
"unknown": "test_unknown",
"passwordHash": "test_passwordHash",
"username": 123, // for auth collections should be casted to string
"emailVisibility": "test_invalid", // for auth collections should be casted to bool
"email": "test_email",
"verified": true,
"tokenKey": "test_tokenKey",
"lastResetSentAt": lastResetSentAt,
"lastVerificationSentAt": "test_lastVerificationSentAt",
}
m := models.NewRecord(collection)
for i, s := range scenarios {
collection.Type = s.collectionType
m.Load(data)
m.IgnoreEmailVisibility(s.exportHidden)
m.WithUnkownData(s.exportUnknown)
exportResult, err := json.Marshal(m.PublicExport())
if err != nil {
t.Errorf("(%d) Unexpected error %v", i, err)
continue
}
exportResultStr := string(exportResult)
// MarshalJSON and PublicExport should return the same
marshalResult, err := m.MarshalJSON()
if err != nil {
t.Errorf("(%d) Unexpected error %v", i, err)
continue
}
marshalResultStr := string(marshalResult)
if exportResultStr != marshalResultStr {
t.Errorf("(%d) Expected the PublicExport to be the same as MarshalJSON, but got \n%v \nvs \n%v", i, exportResultStr, marshalResultStr)
}
if exportResultStr != s.expectedJson {
t.Errorf("(%d) Expected json \n%v \ngot \n%v", i, s.expectedJson, exportResultStr)
}
}
}
func TestRecordUnmarshalJSON(t *testing.T) {
collection := &models.Collection{
Schema: schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeText,
},
&schema.SchemaField{
Name: "field2",
Type: schema.FieldTypeNumber,
},
),
}
data := map[string]any{
"id": "test_id",
"created": "2022-01-01 10:00:00.123Z",
"updated": "2022-01-01 10:00:00.456Z",
"field1": "test_field",
"field2": "123", // should be casted to float
"unknown": "test_unknown",
// auth collection sepcific casting test
"passwordHash": "test_passwordHash",
"emailVisibility": "12345", // should be casted to bool only for auth collections
"username": 123.123, // should be casted to string only for auth collections
"email": "test_email",
"verified": true,
"tokenKey": "test_tokenKey",
"lastResetSentAt": "2022-01-01 11:00:00.000", // should be casted to DateTime only for auth collections
"lastVerificationSentAt": "2022-01-01 12:00:00.000", // should be casted to DateTime only for auth collections
}
dataRaw, err := json.Marshal(data)
if err != nil {
t.Fatalf("Unexpected data marshal error %v", err)
}
scenarios := []struct {
collectionType string
}{
{models.CollectionTypeBase},
{models.CollectionTypeAuth},
}
// with invalid data
m0 := models.NewRecord(collection)
if err := m0.UnmarshalJSON([]byte("test")); err == nil {
t.Fatal("Expected error, got nil")
}
// with valid data (it should be pretty much the same as load)
for i, s := range scenarios {
collection.Type = s.collectionType
m := models.NewRecord(collection)
err := m.UnmarshalJSON(dataRaw)
if err != nil {
t.Errorf("(%d) Unexpected error %v", i, err)
continue
}
expectations := map[string]any{}
for k, v := range data {
expectations[k] = v
}
expectations["created"], _ = types.ParseDateTime("2022-01-01 10:00:00.123Z")
expectations["updated"], _ = types.ParseDateTime("2022-01-01 10:00:00.456Z")
expectations["field2"] = 123.0
// extra casting test
if collection.IsAuth() {
lastResetSentAt, _ := types.ParseDateTime(expectations["lastResetSentAt"])
lastVerificationSentAt, _ := types.ParseDateTime(expectations["lastVerificationSentAt"])
expectations["emailVisibility"] = false
expectations["username"] = "123.123"
expectations["verified"] = true
expectations["lastResetSentAt"] = lastResetSentAt
expectations["lastVerificationSentAt"] = lastVerificationSentAt
}
for k, v := range expectations {
if m.Get(k) != v {
t.Errorf("(%d) Expected field %s to be %v, got %v", i, k, v, m.Get(k))
}
}
}
}
// -------------------------------------------------------------------
// Auth helpers:
// -------------------------------------------------------------------
func TestRecordUsername(t *testing.T) {
scenarios := []struct {
collectionType string
expectError bool
}{
{models.CollectionTypeBase, true},
{models.CollectionTypeAuth, false},
}
testValue := "test 1232 !@#%" // formatting isn't checked
for i, s := range scenarios {
collection := &models.Collection{Type: s.collectionType}
m := models.NewRecord(collection)
if s.expectError {
if err := m.SetUsername(testValue); err == nil {
t.Errorf("(%d) Expected error, got nil", i)
}
if v := m.Username(); v != "" {
t.Fatalf("(%d) Expected empty string, got %q", i, v)
}
// verify that nothing is stored in the record data slice
if v := m.Get(schema.FieldNameUsername); v != nil {
t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameUsername, v)
}
} else {
if err := m.SetUsername(testValue); err != nil {
t.Fatalf("(%d) Expected nil, got error %v", i, err)
}
if v := m.Username(); v != testValue {
t.Fatalf("(%d) Expected %q, got %q", i, testValue, v)
}
// verify that the field is stored in the record data slice
if v := m.Get(schema.FieldNameUsername); v != testValue {
t.Fatalf("(%d) Expected data field value %q, got %q", i, testValue, v)
}
}
}
}
func TestRecordEmail(t *testing.T) {
scenarios := []struct {
collectionType string
expectError bool
}{
{models.CollectionTypeBase, true},
{models.CollectionTypeAuth, false},
}
testValue := "test 1232 !@#%" // formatting isn't checked
for i, s := range scenarios {
collection := &models.Collection{Type: s.collectionType}
m := models.NewRecord(collection)
if s.expectError {
if err := m.SetEmail(testValue); err == nil {
t.Errorf("(%d) Expected error, got nil", i)
}
if v := m.Email(); v != "" {
t.Fatalf("(%d) Expected empty string, got %q", i, v)
}
// verify that nothing is stored in the record data slice
if v := m.Get(schema.FieldNameEmail); v != nil {
t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameEmail, v)
}
} else {
if err := m.SetEmail(testValue); err != nil {
t.Fatalf("(%d) Expected nil, got error %v", i, err)
}
if v := m.Email(); v != testValue {
t.Fatalf("(%d) Expected %q, got %q", i, testValue, v)
}
// verify that the field is stored in the record data slice
if v := m.Get(schema.FieldNameEmail); v != testValue {
t.Fatalf("(%d) Expected data field value %q, got %q", i, testValue, v)
}
}
}
}
func TestRecordEmailVisibility(t *testing.T) {
scenarios := []struct {
collectionType string
value bool
expectError bool
}{
{models.CollectionTypeBase, true, true},
{models.CollectionTypeBase, true, true},
{models.CollectionTypeAuth, false, false},
{models.CollectionTypeAuth, true, false},
}
for i, s := range scenarios {
collection := &models.Collection{Type: s.collectionType}
m := models.NewRecord(collection)
if s.expectError {
if err := m.SetEmailVisibility(s.value); err == nil {
t.Errorf("(%d) Expected error, got nil", i)
}
if v := m.EmailVisibility(); v != false {
t.Fatalf("(%d) Expected empty string, got %v", i, v)
}
// verify that nothing is stored in the record data slice
if v := m.Get(schema.FieldNameEmailVisibility); v != nil {
t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameEmailVisibility, v)
}
} else {
if err := m.SetEmailVisibility(s.value); err != nil {
t.Fatalf("(%d) Expected nil, got error %v", i, err)
}
if v := m.EmailVisibility(); v != s.value {
t.Fatalf("(%d) Expected %v, got %v", i, s.value, v)
}
// verify that the field is stored in the record data slice
if v := m.Get(schema.FieldNameEmailVisibility); v != s.value {
t.Fatalf("(%d) Expected data field value %v, got %v", i, s.value, v)
}
}
}
}
func TestRecordEmailVerified(t *testing.T) {
scenarios := []struct {
collectionType string
value bool
expectError bool
}{
{models.CollectionTypeBase, true, true},
{models.CollectionTypeBase, true, true},
{models.CollectionTypeAuth, false, false},
{models.CollectionTypeAuth, true, false},
}
for i, s := range scenarios {
collection := &models.Collection{Type: s.collectionType}
m := models.NewRecord(collection)
if s.expectError {
if err := m.SetVerified(s.value); err == nil {
t.Errorf("(%d) Expected error, got nil", i)
}
if v := m.Verified(); v != false {
t.Fatalf("(%d) Expected empty string, got %v", i, v)
}
// verify that nothing is stored in the record data slice
if v := m.Get(schema.FieldNameVerified); v != nil {
t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameVerified, v)
}
} else {
if err := m.SetVerified(s.value); err != nil {
t.Fatalf("(%d) Expected nil, got error %v", i, err)
}
if v := m.Verified(); v != s.value {
t.Fatalf("(%d) Expected %v, got %v", i, s.value, v)
}
// verify that the field is stored in the record data slice
if v := m.Get(schema.FieldNameVerified); v != s.value {
t.Fatalf("(%d) Expected data field value %v, got %v", i, s.value, v)
}
}
}
}
func TestRecordTokenKey(t *testing.T) {
scenarios := []struct {
collectionType string
expectError bool
}{
{models.CollectionTypeBase, true},
{models.CollectionTypeAuth, false},
}
testValue := "test 1232 !@#%" // formatting isn't checked
for i, s := range scenarios {
collection := &models.Collection{Type: s.collectionType}
m := models.NewRecord(collection)
if s.expectError {
if err := m.SetTokenKey(testValue); err == nil {
t.Errorf("(%d) Expected error, got nil", i)
}
if v := m.TokenKey(); v != "" {
t.Fatalf("(%d) Expected empty string, got %q", i, v)
}
// verify that nothing is stored in the record data slice
if v := m.Get(schema.FieldNameTokenKey); v != nil {
t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameTokenKey, v)
}
} else {
if err := m.SetTokenKey(testValue); err != nil {
t.Fatalf("(%d) Expected nil, got error %v", i, err)
}
if v := m.TokenKey(); v != testValue {
t.Fatalf("(%d) Expected %q, got %q", i, testValue, v)
}
// verify that the field is stored in the record data slice
if v := m.Get(schema.FieldNameTokenKey); v != testValue {
t.Fatalf("(%d) Expected data field value %q, got %q", i, testValue, v)
}
}
}
}
func TestRecordRefreshTokenKey(t *testing.T) {
scenarios := []struct {
collectionType string
expectError bool
}{
{models.CollectionTypeBase, true},
{models.CollectionTypeAuth, false},
}
for i, s := range scenarios {
collection := &models.Collection{Type: s.collectionType}
m := models.NewRecord(collection)
if s.expectError {
if err := m.RefreshTokenKey(); err == nil {
t.Errorf("(%d) Expected error, got nil", i)
}
if v := m.TokenKey(); v != "" {
t.Fatalf("(%d) Expected empty string, got %q", i, v)
}
// verify that nothing is stored in the record data slice
if v := m.Get(schema.FieldNameTokenKey); v != nil {
t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameTokenKey, v)
}
} else {
if err := m.RefreshTokenKey(); err != nil {
t.Fatalf("(%d) Expected nil, got error %v", i, err)
}
if v := m.TokenKey(); len(v) != 50 {
t.Fatalf("(%d) Expected 50 chars, got %d", i, len(v))
}
// verify that the field is stored in the record data slice
if v := m.Get(schema.FieldNameTokenKey); v != m.TokenKey() {
t.Fatalf("(%d) Expected data field value %q, got %q", i, m.TokenKey(), v)
}
}
}
}
func TestRecordLastResetSentAt(t *testing.T) {
scenarios := []struct {
collectionType string
expectError bool
}{
{models.CollectionTypeBase, true},
{models.CollectionTypeAuth, false},
}
testValue, err := types.ParseDateTime("2022-01-01 00:00:00.123Z")
if err != nil {
t.Fatal(err)
}
for i, s := range scenarios {
collection := &models.Collection{Type: s.collectionType}
m := models.NewRecord(collection)
if s.expectError {
if err := m.SetLastResetSentAt(testValue); err == nil {
t.Errorf("(%d) Expected error, got nil", i)
}
if v := m.LastResetSentAt(); !v.IsZero() {
t.Fatalf("(%d) Expected empty value, got %v", i, v)
}
// verify that nothing is stored in the record data slice
if v := m.Get(schema.FieldNameLastResetSentAt); v != nil {
t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameLastResetSentAt, v)
}
} else {
if err := m.SetLastResetSentAt(testValue); err != nil {
t.Fatalf("(%d) Expected nil, got error %v", i, err)
}
if v := m.LastResetSentAt(); v != testValue {
t.Fatalf("(%d) Expected %v, got %v", i, testValue, v)
}
// verify that the field is stored in the record data slice
if v := m.Get(schema.FieldNameLastResetSentAt); v != testValue {
t.Fatalf("(%d) Expected data field value %v, got %v", i, testValue, v)
}
}
}
}
func TestRecordLastVerificationSentAt(t *testing.T) {
scenarios := []struct {
collectionType string
expectError bool
}{
{models.CollectionTypeBase, true},
{models.CollectionTypeAuth, false},
}
testValue, err := types.ParseDateTime("2022-01-01 00:00:00.123Z")
if err != nil {
t.Fatal(err)
}
for i, s := range scenarios {
collection := &models.Collection{Type: s.collectionType}
m := models.NewRecord(collection)
if s.expectError {
if err := m.SetLastVerificationSentAt(testValue); err == nil {
t.Errorf("(%d) Expected error, got nil", i)
}
if v := m.LastVerificationSentAt(); !v.IsZero() {
t.Fatalf("(%d) Expected empty value, got %v", i, v)
}
// verify that nothing is stored in the record data slice
if v := m.Get(schema.FieldNameLastVerificationSentAt); v != nil {
t.Fatalf("(%d) Didn't expect data field %q: %v", i, schema.FieldNameLastVerificationSentAt, v)
}
} else {
if err := m.SetLastVerificationSentAt(testValue); err != nil {
t.Fatalf("(%d) Expected nil, got error %v", i, err)
}
if v := m.LastVerificationSentAt(); v != testValue {
t.Fatalf("(%d) Expected %v, got %v", i, testValue, v)
}
// verify that the field is stored in the record data slice
if v := m.Get(schema.FieldNameLastVerificationSentAt); v != testValue {
t.Fatalf("(%d) Expected data field value %v, got %v", i, testValue, v)
}
}
}
}
func TestRecordPasswordHash(t *testing.T) {
m := models.NewRecord(&models.Collection{})
if v := m.PasswordHash(); v != "" {
t.Errorf("Expected PasswordHash() to be empty, got %v", v)
}
m.Set(schema.FieldNamePasswordHash, "test")
if v := m.PasswordHash(); v != "test" {
t.Errorf("Expected PasswordHash() to be 'test', got %v", v)
}
}
func TestRecordValidatePassword(t *testing.T) {
// 123456
hash := "$2a$10$YKU8mPP8sTE3xZrpuM.xQuq27KJ7aIJB2oUeKPsDDqZshbl5g5cDK"
scenarios := []struct {
collectionType string
password string
hash string
expected bool
}{
{models.CollectionTypeBase, "123456", hash, false},
{models.CollectionTypeAuth, "", "", false},
{models.CollectionTypeAuth, "", hash, false},
{models.CollectionTypeAuth, "123456", hash, true},
{models.CollectionTypeAuth, "654321", hash, false},
}
for i, s := range scenarios {
collection := &models.Collection{Type: s.collectionType}
m := models.NewRecord(collection)
m.Set(schema.FieldNamePasswordHash, hash)
if v := m.ValidatePassword(s.password); v != s.expected {
t.Errorf("(%d) Expected %v, got %v", i, s.expected, v)
}
}
}
func TestRecordSetPassword(t *testing.T) {
scenarios := []struct {
collectionType string
password string
expectError bool
}{
{models.CollectionTypeBase, "", true},
{models.CollectionTypeBase, "123456", true},
{models.CollectionTypeAuth, "", true},
{models.CollectionTypeAuth, "123456", false},
}
for i, s := range scenarios {
collection := &models.Collection{Type: s.collectionType}
m := models.NewRecord(collection)
if s.expectError {
if err := m.SetPassword(s.password); err == nil {
t.Errorf("(%d) Expected error, got nil", i)
}
if v := m.GetString(schema.FieldNamePasswordHash); v != "" {
t.Errorf("(%d) Expected empty hash, got %q", i, v)
}
} else {
if err := m.SetPassword(s.password); err != nil {
t.Errorf("(%d) Expected nil, got err", i)
}
if v := m.GetString(schema.FieldNamePasswordHash); v == "" {
t.Errorf("(%d) Expected non empty hash", i)
}
if !m.ValidatePassword(s.password) {
t.Errorf("(%d) Expected true, got false", i)
}
}
}
}