package main import ( "encoding/json" "errors" "fmt" "reflect" "strings" "testing" ) // ADR 0329 §10, §12: derived from every desktop node's profile, laid out per layer, and proposed one layer at a // time, carrying the layer as it stands. // derivingMesh is two desktop nodes, g14 (this one, f) and shanks (f2), and a server that holds no Betterbird, as the // controller answers them; layers are the controller's layers, open the proposals that wait. type derivingMesh struct { t *testing.T shanks *fake layers map[string]map[string]any open bool proposed []map[string]any asked []string asleep bool // garbled answers the layer in words that are not its JSON. garbled bool } func (d *derivingMesh) ask(key string, body any) (json.RawMessage, error) { d.asked = append(d.asked, key) args, _ := body.(map[string]any) answer := func(ok bool, text string) (json.RawMessage, error) { b, _ := json.Marshal(map[string]any{"ok": ok, "output": text}) return b, nil } switch { case key == "seat:mesh-controller.nodes": return answer(true, `[{"name": "g14"}, {"name": "novox"}, {"name": "shanks"}]`) case key == "seat:mesh-controller.node": if args["node"] == "novox" { return answer(true, "novox\n assigned: postgres, nats\n") } return answer(true, fmt.Sprintf("%s\n last heard from here\n assigned: adwaita, betterbird, dunst\n", args["node"])) case key == "betterbird.betterbird_derive@shanks": if d.asleep { return nil, errors.New("nats: no responders available for request") } here, err := d.shanks.deriveHere(stringsOf(args["kinds"])) if err != nil { d.t.Fatal(err) } text, _ := json.Marshal(credentialChecked(DeriveAnswer{Here: &here})) return json.Marshal(map[string]any{"content": []any{map[string]any{"type": "text", "text": string(text)}}}) case key == "seat:mesh-controller.settings" && args["proposals"] == "true": if d.open { return answer(true, "sab12 set betterbird on the whole mesh (accounts, identities)\n by g14.node-tools at 2026-10-13 01:00; fingerprint ab12\n waiting for the operator until 2026-10-14 01:00\n"+ "scd34 set mounts on shanks (sources)\n by g14.node-tools at 2026-10-11 00:05; fingerprint dd0b\n chosen: done\n") } return answer(true, "scd34 set mounts on shanks (sources)\n by g14.node-tools; fingerprint dd0b\n chosen: done\n") case key == "seat:mesh-controller.settings" && args["propose"] == "true": d.proposed = append(d.proposed, args) return answer(true, "proposal sab12: Set the settings of betterbird?\n fingerprint ab12") case key == "seat:mesh-controller.settings": node, _ := args["node"].(string) if d.garbled { return answer(true, "Settings of betterbird on "+node+":\n accounts = {…}\n") } layer := d.layers[node] if layer == nil { where := node if where == "" { where = "the whole mesh" } return answer(true, "betterbird on "+where+": no layer — the module's definition says\nbetterbird on "+where+", every value") } b, _ := json.MarshalIndent(layer, "", " ") return answer(true, string(b)+"\n approved by the operator\nbetterbird on the whole mesh, every value and where it comes from:\n") } d.t.Fatalf("asked %s %v", key, body) return nil, nil } func stringsOf(v any) []string { switch x := v.(type) { case []string: return x case []any: var out []string for _, s := range x { out = append(out, s.(string)) } return out } return nil } func TestDeriveLaysEachEntryOutPerLayerAndProposesOneLayerAtATime(t *testing.T) { f := newFake(t) f.profileWithAccounts() f.profileWithConfiguration() f.write(profileDir+"/prefs.js", f.read(profileDir+"/prefs.js")+ `user_pref("calendar.registry.c6.type", "caldav");`+"\n"+ `user_pref("calendar.registry.c6.uri", "https://other:hunter2@dav.example.org/cal/x/");`+"\n"+ `user_pref("calendar.registry.c6.name", "Old");`+"\n") f.declareAccounts(adoptable) links(t, nil) f2 := newFake(t) f2.profileWithAccounts() f2.profileWithConfiguration() // shanks holds the same accounts and calendars, another signature, another tag colour and a calendar of its own. f2.write(profileDir+"/prefs.js", strings.NewReplacer(`"Regards"`, `"Cheers"`, `"#FF0000"`, `"#CC0000"`).Replace(f2.read(profileDir+"/prefs.js"))+ `user_pref("calendar.registry.s1.type", "caldav");`+"\n"+ `user_pref("calendar.registry.s1.uri", "https://dav.example.org/cal/standup/");`+"\n"+ `user_pref("calendar.registry.s1.name", "Standup");`+"\n") f2.declareAccounts(adoptable) mesh := &derivingMesh{t: t, shanks: f2, layers: map[string]map[string]any{"": {"accounts": map[string]any{"mail": "as approved"}}}} oldNode, oldAsk := nodeName, asker t.Cleanup(func() { nodeName, asker = oldNode, oldAsk }) nodeName = func() string { return "g14" } asker = mesh.ask a, err := f.Derive(nil, false, false, nil, "") if err != nil { t.Fatal(err) } if !reflect.DeepEqual(a.From, []string{"g14", "shanks"}) || len(a.NotHeard) != 0 { t.Fatalf("derived from the nodes holding Betterbird: %v %v", a.From, a.NotHeard) } whole := mustJSON(a) mw := a.Layers[meshWide] ids, _ := mw[kindIdentities].(map[string]any) if mail, _ := ids["mail"].(map[string]any); mail == nil || mustJSON(mail["signature"]) != `["Regards"]` || mail["address"] != nil { t.Errorf("an identity on both nodes is mesh-wide with this node's value, the default without its address: %v", ids) } cals, _ := mw[kindCalendars].(map[string]any) if cals["home"] == nil || cals["standup"] != nil { t.Errorf("a calendar both hold is mesh-wide, one only shanks holds is not: %v", cals) } if sh := mustJSON(a.Layers["shanks"]); !strings.Contains(sh, "https://dav.example.org/cal/standup/") { t.Errorf("an entry on one node only goes into that node's layer: %s", sh) } tags, _ := mw[kindTags].(map[string]any) if l1, _ := tags["$label1"].(map[string]any); l1 == nil || l1["color"] != "#FF0000" { t.Errorf("the tag is mesh-wide with this node's colour: %v", tags) } diffs := map[string]difference{} for _, d := range a.Differences { diffs[d.Kind+"/"+d.Entry+"/"+d.Field] = d } if d := diffs["identities/mail/signature"]; d.Node != "shanks" || mustJSON(d.There) != `["Cheers"]` { t.Errorf("the answer lists the field shanks holds differently: %+v", a.Differences) } if d := diffs["tags/$label1/color"]; d.There != "#CC0000" || d.MeshWide != "#FF0000" { t.Errorf("%+v", a.Differences) } for _, never := range []string{"secret-token", "hunter2", "owl://", `"Work"`} { if strings.Contains(mustJSON(a.Layers), never) { t.Errorf("no ICS address, credential or Owl calendar is derived: %s in %s", never, mustJSON(a.Layers)) } } if !strings.Contains(whole, "Holidays") || !strings.Contains(whole, "‹withheld›") || strings.Contains(mustJSON(credentialChecked(a)), "hunter2") { t.Errorf("what is left out is named, an ICS calendar by its name and a password as ‹withheld›: %v", a.NotDerived) } if len(mesh.proposed) != 0 { t.Fatal("without propose nothing is proposed") } // Propose: the mesh-wide layer first, as it stands with the derived kinds, and the operator's setup of prefs. prefs := map[string]any{} for n, v := range shippedDefaultsWant { prefs[n], _ = prefValue(v) } a, err = f.Derive(nil, false, true, prefs, "") if err != nil { t.Fatal(err) } if len(mesh.proposed) != 1 || mesh.proposed[0]["node"] != nil || mesh.proposed[0]["replace"] != "true" || a.Next != meshWide { t.Fatalf("the mesh-wide layer is proposed first: %v %s", mesh.proposed, a.Next) } var values map[string]any _ = json.Unmarshal([]byte(mesh.proposed[0]["values"].(string)), &values) if values["accounts"] == nil || values[kindIdentities] == nil || values[kindTags] == nil || values["prefs"] == nil { t.Fatalf("the layer as it stands, with every derived kind and prefs: %v", values) } body, _ := json.Marshal(map[string]any{"prefs": values["prefs"]}) if got, err := parseDeclaredPrefs(body); err != nil || !reflect.DeepEqual(got, shippedDefaultsWant) { t.Fatalf("the operator's 21 preferences, one entry each: %v %v", got, err) } if !strings.Contains(a.Proposed, "sab12") { t.Fatalf("%s", a.Proposed) } // While it waits nothing more is proposed. mesh.open = true if _, err := f.Derive(nil, false, true, nil, ""); err == nil || !strings.Contains(err.Error(), "sab12 (the whole mesh)") || len(mesh.proposed) != 1 { t.Fatalf("one open proposal of the module's at a time: %v", err) } // Approved: the mesh-wide layer holds it, so the next layer is shanks's own. mesh.open = false mesh.layers[""] = values a, err = f.Derive(nil, false, true, prefs, "") if err != nil { t.Fatal(err) } if len(mesh.proposed) != 2 || mesh.proposed[1]["node"] != "shanks" || a.Next != "shanks" || !strings.Contains(mesh.proposed[1]["values"].(string), "standup") { t.Fatalf("then the node's own layer: %v", mesh.proposed) } var shanks map[string]any if err := json.Unmarshal([]byte(mesh.proposed[1]["values"].(string)), &shanks); err != nil { t.Fatal(err) } mesh.layers["shanks"] = shanks if a, err = f.Derive(nil, false, true, prefs, ""); err != nil || len(mesh.proposed) != 2 || !strings.HasPrefix(a.Next, "none") { t.Fatalf("nothing left to propose: %v %s", err, a.Next) } // A desktop node that does not answer: derived without it, never proposed. mesh.asleep = true a, err = f.Derive(nil, false, true, nil, meshWide) if err == nil || len(a.NotHeard) != 1 || !strings.Contains(err.Error(), "did not answer") || len(mesh.proposed) != 2 { t.Fatalf("a layer is never proposed without a desktop node: %v %v", err, a.NotHeard) } // A key to adopt is a node's: the mesh-wide layer's proposal refuses one. if err := proposable(map[string]any{kindCalendars: map[string]any{"c": map[string]any{"name": "C", "url": "https://dav.example.org/c/", "key": "c3"}}}, true); err == nil || !strings.Contains(err.Error(), "node's own layer") { t.Fatalf("%v", err) } } // Review 121: a layer the controller answers in any other words than its JSON or "no layer" refuses the proposal, // because a proposal replaces the layer whole and would drop accounts. func TestALayerThatCannotBeReadRefusesTheProposal(t *testing.T) { f := newFake(t) f.profileWithAccounts() f.profileWithConfiguration() f.declareAccounts(adoptable) links(t, nil) f2 := newFake(t) f2.profileWithAccounts() f2.declareAccounts(adoptable) mesh := &derivingMesh{t: t, shanks: f2, garbled: true} oldNode, oldAsk := nodeName, asker t.Cleanup(func() { nodeName, asker = oldNode, oldAsk }) nodeName = func() string { return "g14" } asker = mesh.ask if _, err := f.Derive(nil, false, true, nil, ""); err == nil || !strings.Contains(err.Error(), "cannot be read as the layer") || len(mesh.proposed) != 0 { t.Fatalf("the derive refuses: %v %v", err, mesh.proposed) } if _, err := f.ProfileAccounts(true); err == nil || !strings.Contains(err.Error(), "cannot be read as the layer") || len(mesh.proposed) != 0 { t.Fatalf("betterbird_accounts refuses: %v %v", err, mesh.proposed) } if _, err := readLayer(""); err == nil { t.Fatal("an unreadable layer is an error, never an empty layer") } mesh.garbled = false if got, err := readLayer("shanks"); err != nil || len(got) != 0 { t.Fatalf("no layer, in the controller's words, is an empty layer: %v %v", got, err) } }