Files
DriverVault/API Server/internal/bootstrap/bootstrap_test.go
T
tajniak81andClaude Opus 4.8 30c9cdebe9 Add production Docker compose + on-startup PocketBase bootstrap
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>
2026-07-19 15:35:33 +02:00

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)
}
}
}