48614e622d
The dictionary lives on a persistent volume seeded from the image once and never re-seeded, so a redeploy that bumped BACKEND_DICT_VERSION left the new DAWGs unreachable (the volume mount shadows the image copy) and the active version stuck at whatever the admin console last installed — a native offline-first client then fetched the server's older pinned version over the network instead of using its bundled dict. On boot the backend now DELIVERS the build version onto the volume add-only, from a second unshadowed image copy at BACKEND_DICT_SEED_DIR, into DICT_DIR/<version>/ when absent (the flat seed and prior uploads untouched, so in-flight games keep theirs), and InitActiveVersion makes the build version active — except a console-installed version NEWER than the build (compared numerically) is not downgraded by a restart. The .seed_version guard still protects the flat seed's label (a bump is a new subdirectory, never a relabel). The console stays for out-of-band updates. Docs: ARCHITECTURE.md §5, deploy/README.md, seedmarker.go. Tests: engine.DeliverVersion (add-only, idempotent, flat-seed skip), compareDictVersions, and the dictionary-update integration test's deploy-delivers path.
69 lines
2.2 KiB
Go
69 lines
2.2 KiB
Go
package engine
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestDeliverVersion(t *testing.T) {
|
|
// A blank seed directory disables delivery (the pre-existing seed-only behaviour).
|
|
if err := DeliverVersion(t.TempDir(), "", "v1.3.1"); err != nil {
|
|
t.Fatalf("blank seedDir: %v", err)
|
|
}
|
|
|
|
// An existing volume flat-seeded as v1.3.0 gets v1.3.1 delivered as a subdirectory,
|
|
// add-only: the flat seed's marker is left intact and the new version's DAWGs land.
|
|
dict := t.TempDir()
|
|
seed := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dict, seedMarkerFile), []byte("v1.3.0\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, f := range dictFiles {
|
|
if err := os.WriteFile(filepath.Join(seed, f), []byte("dawg:"+f), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := DeliverVersion(dict, seed, "v1.3.1"); err != nil {
|
|
t.Fatalf("deliver v1.3.1: %v", err)
|
|
}
|
|
for _, f := range dictFiles {
|
|
got, err := os.ReadFile(filepath.Join(dict, "v1.3.1", f))
|
|
if err != nil {
|
|
t.Fatalf("delivered %s: %v", f, err)
|
|
}
|
|
if string(got) != "dawg:"+f {
|
|
t.Errorf("delivered %s = %q, want the seed bytes", f, got)
|
|
}
|
|
}
|
|
if m, _ := os.ReadFile(filepath.Join(dict, seedMarkerFile)); string(m) != "v1.3.0\n" {
|
|
t.Errorf("flat seed marker relabelled to %q (delivery must be add-only)", m)
|
|
}
|
|
// Idempotent: a second call is a no-op and leaves no staging directory behind.
|
|
if err := DeliverVersion(dict, seed, "v1.3.1"); err != nil {
|
|
t.Fatalf("re-deliver v1.3.1: %v", err)
|
|
}
|
|
if entries, _ := os.ReadDir(dict); hasStaging(entries) {
|
|
t.Errorf("a staging directory survived delivery")
|
|
}
|
|
|
|
// On a fresh directory the build version becomes the flat seed, so nothing is delivered
|
|
// as a redundant subdirectory.
|
|
fresh := t.TempDir()
|
|
if err := DeliverVersion(fresh, seed, "v1.3.1"); err != nil {
|
|
t.Fatalf("fresh deliver: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(fresh, "v1.3.1")); !os.IsNotExist(err) {
|
|
t.Errorf("fresh volume got a redundant v1.3.1 subdir; it should be the flat seed")
|
|
}
|
|
}
|
|
|
|
func hasStaging(entries []os.DirEntry) bool {
|
|
for _, e := range entries {
|
|
if len(e.Name()) >= len(".staging") && e.Name()[:len(".staging")] == ".staging" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|