190 lines
6.8 KiB
Go
190 lines
6.8 KiB
Go
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" }
|