package cabana import ( "encoding/json" "strings" "testing" "testing/fstest" ) func TestRelationSchema(t *testing.T) { ctl := relationTestController{} fsys := relationTestFS(validRelationYAML) form, err := CompileForm("acme.demo", ctl, fsys) if err != nil { t.Fatal(err) } relations, err := compileRelations("acme.demo", ctl, fsys, form) if err != nil { t.Fatal(err) } compiled := relations["editors"] if compiled == nil || compiled.Contract.ParentForeignKey != "owner_id" || compiled.Contract.RelatedForeignKey != "target_id" { t.Fatalf("compiled=%#v", compiled) } raw, err := json.Marshal(compiled.Schema) if err != nil { t.Fatal(err) } got := string(raw) for _, want := range []string{`"name":"editors"`, `"key":"username"`, `"key":"email"`, `"toolbarButtons":["link","unlink"]`, `"showSearch":true`} { if !strings.Contains(got, want) { t.Fatalf("schema missing %s: %s", want, got) } } if strings.Index(got, `"key":"username"`) >= strings.Index(got, `"key":"email"`) { t.Fatalf("column order drifted: %s", got) } for _, tc := range []struct { name string yaml string provider relationTestController want string }{ {name: "unknown key", yaml: strings.Replace(validRelationYAML, "showSearch: true", "showSearch: true\n conditions: unsafe", 1), want: "conditions"}, {name: "unsupported action", yaml: strings.Replace(validRelationYAML, "link|unlink", "link|execute", 1), want: "unsupported relation action execute"}, {name: "missing relation", yaml: "other:\n label: other\n view:\n list:\n columns: {}\n manage:\n list:\n columns: {}\n", want: "has no relation-manager field"}, {name: "bad target column", yaml: validRelationYAML, provider: relationTestController{badColumn: true}, want: "target is missing column missing"}, } { t.Run(tc.name, func(t *testing.T) { probe := tc.provider form, err := CompileForm("acme.demo", probe, relationTestFS(tc.yaml)) if err != nil { t.Fatal(err) } _, err = compileRelations("acme.demo", probe, relationTestFS(tc.yaml), form) if err == nil || !strings.Contains(err.Error(), tc.want) { t.Fatalf("err=%v want=%q", err, tc.want) } }) } } func TestRelationEmpty(t *testing.T) { raw, err := json.Marshal(RelationSchema{Name: "empty"}) if err != nil { t.Fatal(err) } got := string(raw) if strings.Count(got, `"columns":[]`) != 2 || strings.Count(got, `"toolbarButtons":[]`) != 2 || strings.Contains(got, "null") { t.Fatalf("empty relation collections=%s", got) } } func TestRelationSingle(t *testing.T) { schema := RelationSchema{Name: "editors", View: RelationPanel{List: RelationList{Columns: []RelationColumn{{Key: "email", Label: "email", Searchable: true, Sortable: true}}}}} raw, err := json.Marshal(schema) if err != nil { t.Fatal(err) } if strings.Count(string(raw), `"key":"email"`) != 1 { t.Fatalf("single column=%s", raw) } } func TestRelationStablePages(t *testing.T) { schema := &RelationSchema{View: RelationPanel{List: RelationList{Columns: []RelationColumn{{Key: "username", Sortable: true}, {Key: "email", Sortable: true}}}}} page, per, key, desc, err := normalizeRelationQuery(schema, false, RelationQuery{Page: "2", PerPage: "20", Sort: "username", Dir: "asc"}) if err != nil || page != 2 || per != 20 || key != "username" || desc { t.Fatalf("normalized=%d/%d %q desc=%v err=%v", page, per, key, desc, err) } if _, _, _, _, err := normalizeRelationQuery(schema, false, RelationQuery{Sort: "Username"}); err == nil { t.Fatal("case-changed sort survived") } } func TestRelationCandidateExclusions(t *testing.T) { contract := relationTestController{}.AdminRelationContracts()[0] ids, err := contract.ExcludedRelatedIDs(&relationTestOwner{OwnerID: 41}) if err != nil || len(ids) != 1 || ids[0] != 41 { t.Fatalf("excluded=%v err=%v", ids, err) } if _, err := contract.ExcludedRelatedIDs(&relationTestTarget{}); err == nil { t.Fatal("wrong parent type survived") } } const validRelationYAML = `editors: label: demo.editors view: list: columns: username: label: demo.username email: label: demo.email toolbarButtons: link|unlink showSearch: true manage: list: columns: username: label: demo.username email: label: demo.email showSearch: true ` func relationTestFS(relation string) fstest.MapFS { return fstest.MapFS{ "controllers/owners/config_form.yaml": {Data: []byte("name: owners\nform: ~/plugins/acme/demo/models/owner/fields.yaml\nmodelClass: Owner\n")}, "controllers/owners/config_relation.yaml": {Data: []byte(relation)}, "models/owner/fields.yaml": {Data: []byte("fields:\n editors:\n type: relation-manager\n relation: editors\n")}, } } type relationTestController struct{ badColumn bool } func (relationTestController) ID() string { return "acme.demo.owners" } func (relationTestController) ModelName() string { return "Owner" } func (relationTestController) ConfigDir() string { return "controllers/owners" } func (relationTestController) NewRecord() any { return &relationTestOwner{} } func (c relationTestController) AdminRelationContracts() []RelationContract { columns := map[string]string{"username": "username", "email": "email"} if c.badColumn { columns["email"] = "missing" } return []RelationContract{{ Name: "editors", NewRelated: func() any { return &relationTestTarget{} }, NewPivot: func() any { return &relationTestPivot{} }, ParentForeignKey: "owner_id", RelatedForeignKey: "target_id", Columns: columns, HookPivotColumns: []string{"role"}, ExcludedRelatedIDs: func(parent any) ([]uint, error) { owner, ok := parent.(*relationTestOwner) if !ok { return nil, &relationTestError{} } return []uint{owner.OwnerID}, nil }, }} } type relationTestOwner struct { ID uint `gorm:"column:id;primaryKey"` OwnerID uint `gorm:"column:owner_id"` Editors []relationTestTarget `gorm:"many2many:relation_test_pivots"` } func (*relationTestOwner) TableName() string { return "relation_test_owners" } type relationTestTarget struct { ID uint `gorm:"column:id;primaryKey"` Username string `gorm:"column:username"` Email string `gorm:"column:email"` } func (*relationTestTarget) TableName() string { return "relation_test_targets" } type relationTestPivot struct { ID uint `gorm:"column:id;primaryKey"` OwnerID uint `gorm:"column:owner_id"` TargetID uint `gorm:"column:target_id"` Role string `gorm:"column:role"` } func (*relationTestPivot) TableName() string { return "relation_test_pivots" } type relationTestError struct{} func (*relationTestError) Error() string { return "wrong relation parent" }