Introduce registry-pull production stacks (docker-compose.prod.yml) for both the multi-container Docker setup and the all-in-one Docker AIO image, with everything an operator needs (superuser, super-admin, ports, volumes) driven from .env. The API Server now bootstraps PocketBase on startup: a new internal/bootstrap package (Go port of setup-pocketbase.mjs) creates missing collections, reconciles existing ones, and creates the DriverVault super-admin from DRIVERVAULT_SUPERADMIN_* when absent. Idempotent and gated by PB_BOOTSTRAP. The PocketBase superuser is still upserted by the PocketBase container, since the REST API cannot bootstrap the first superuser. Move PocketBase to port 8070 (internal + published) and the web app to 8090 across both stacks, with matching CORS defaults. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
97 lines
3.2 KiB
Go
97 lines
3.2 KiB
Go
package bootstrap
|
|
|
|
import "testing"
|
|
|
|
// TestRenderFieldModern locks the v0.23+ ("fields") wire shape for each field
|
|
// type against what PocketBase expects.
|
|
func TestRenderFieldModern(t *testing.T) {
|
|
idByName := map[string]string{"cars": "col_cars"}
|
|
|
|
rel := renderField(fRelation("car", "cars", true, true), "fields", idByName)
|
|
if rel["collectionId"] != "col_cars" || rel["cascadeDelete"] != true ||
|
|
rel["maxSelect"] != 1 || rel["minSelect"] != 0 || rel["required"] != true {
|
|
t.Fatalf("relation rendered wrong: %#v", rel)
|
|
}
|
|
|
|
nonCascade := renderField(fRelation("owner", "users", false, false), "fields", map[string]string{"users": "u"})
|
|
if nonCascade["cascadeDelete"] != false {
|
|
t.Fatalf("non-cascading relation should keep cascadeDelete=false: %#v", nonCascade)
|
|
}
|
|
|
|
sel := renderField(fSelect("result", []string{"passed", "failed"}, false), "fields", idByName)
|
|
if sel["maxSelect"] != 1 {
|
|
t.Fatalf("select maxSelect: %#v", sel)
|
|
}
|
|
if vals, ok := sel["values"].([]string); !ok || len(vals) != 2 {
|
|
t.Fatalf("select values: %#v", sel["values"])
|
|
}
|
|
|
|
ad := renderField(fAutodate("created", true, false), "fields", idByName)
|
|
if ad["onCreate"] != true || ad["onUpdate"] != false {
|
|
t.Fatalf("autodate: %#v", ad)
|
|
}
|
|
|
|
file := renderField(attachment(), "fields", idByName)
|
|
if file["maxSize"] != 10485760 || file["maxSelect"] != 1 {
|
|
t.Fatalf("file: %#v", file)
|
|
}
|
|
if mt, ok := file["mimeTypes"].([]string); !ok || len(mt) != 5 {
|
|
t.Fatalf("file mimeTypes: %#v", file["mimeTypes"])
|
|
}
|
|
|
|
j := renderField(fJSON("params", 10000), "fields", idByName)
|
|
if j["maxSize"] != 10000 {
|
|
t.Fatalf("json: %#v", j)
|
|
}
|
|
}
|
|
|
|
// TestRenderFieldLegacy checks that the legacy ("schema") layout nests options.
|
|
func TestRenderFieldLegacy(t *testing.T) {
|
|
rel := renderField(fRelation("car", "cars", true, true), "schema", map[string]string{"cars": "col_cars"})
|
|
opts, ok := rel["options"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("legacy relation should nest options: %#v", rel)
|
|
}
|
|
if opts["collectionId"] != "col_cars" || opts["cascadeDelete"] != true {
|
|
t.Fatalf("legacy relation options: %#v", opts)
|
|
}
|
|
}
|
|
|
|
// TestSameValues covers the select-reconcile comparison (order-insensitive).
|
|
func TestSameValues(t *testing.T) {
|
|
if !sameValues([]any{"a", "b"}, []string{"b", "a"}) {
|
|
t.Fatal("same set in different order should compare equal")
|
|
}
|
|
if sameValues([]any{"a"}, []string{"a", "b"}) {
|
|
t.Fatal("differing lengths should not be equal")
|
|
}
|
|
if sameValues([]any{"a", "c"}, []string{"a", "b"}) {
|
|
t.Fatal("differing members should not be equal")
|
|
}
|
|
if sameValues(nil, []string{"a"}) {
|
|
t.Fatal("nil current should not equal non-empty want")
|
|
}
|
|
}
|
|
|
|
// TestSchemaConsistency guards the ordered lists against the schema map.
|
|
func TestSchemaConsistency(t *testing.T) {
|
|
for _, name := range createOrder {
|
|
if _, ok := collectionsSchema[name]; !ok {
|
|
t.Errorf("createOrder references unknown collection %q", name)
|
|
}
|
|
if name == "users" {
|
|
t.Errorf("users must not be in createOrder (built-in auth collection)")
|
|
}
|
|
}
|
|
for _, name := range reconcileOrder {
|
|
if _, ok := collectionsSchema[name]; !ok {
|
|
t.Errorf("reconcileOrder references unknown collection %q", name)
|
|
}
|
|
}
|
|
for name := range indexes {
|
|
if _, ok := collectionsSchema[name]; !ok {
|
|
t.Errorf("indexes references unknown collection %q", name)
|
|
}
|
|
}
|
|
}
|