mesh/merge-gate head 1835f0efad0f merged into trunk 39e7a06e6293 — pass: builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person; every machin…
mesh/repo-check head 1835f0efad0f merged into trunk 39e7a06e6293 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery superseded: a newer head of the same pull request
The module owned only the accounts' server fields, so identities, signatures, calendars, address books, filters and tags could differ between desktop nodes unseen, and the operator's own preferences were shipped in the catalogue. prefs becomes one entry per preference (a node's layer overrides one key) and the defaults file keeps no value of the operator's; identities, calendars, address-books, filters and tags become settings, adopted like accounts, filters owned whole per account, and what Betterbird rewrites written only while it is closed. New tools list each kind, derive the layers from every desktop node and propose one at a time, and show drift; every answer passes a credential check.
542 lines
23 KiB
Go
542 lines
23 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/zip"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The configuration the module ships (novox/hq issue 345): the policy's locks, the module's defaults file, and the
|
|
// tools that show the layers, flag drift, clear it and propose a node layer.
|
|
|
|
// shippedDefaultsWant is the setup the operator approved on 2026-10-12 (the laptop's, with reminders missed while
|
|
// asleep shown at the next start and chat off), which the module shipped as its defaults file until issue 586 moved
|
|
// it into the mesh-wide layer of the setting prefs: the catalogue holds none of it now.
|
|
var shippedDefaultsWant = map[string]string{
|
|
"mail.uidensity": "0",
|
|
"mail.uifontsize": "14",
|
|
"mail.dark-reader.enabled": "false",
|
|
"mail.pane_config.dynamic": "2",
|
|
"mail.pane_config.multiline_all": "true",
|
|
"mail.threadpane.listview": "0",
|
|
"mailnews.default_view_flags": "1",
|
|
"mail.warn_on_shift_delete": "false",
|
|
"mail.biff.show_alert": "false",
|
|
"mail.biff.play_sound": "false",
|
|
"mail.biff.show_tray_icon": "false",
|
|
"mail.biff.use_system_alert": "true",
|
|
"calendar.view.daystarthour": "9",
|
|
"calendar.view.dayendhour": "20",
|
|
"calendar.item.promptDelete": "false",
|
|
"calendar.timezone.useSystemTimezone": "true",
|
|
"calendar.alarms.show": "true",
|
|
"calendar.alarms.showmissed": "true",
|
|
"mail.chat.enabled": "false",
|
|
"mailnews.message_display.disable_remote_image": "true",
|
|
"privacy.donottrackheader.enabled": "true",
|
|
}
|
|
|
|
var shippedKeepsWant = []string{"extensions.activeThemeID", "browser.display.use_system_colors",
|
|
"layout.css.always_underline_links", "mail.spam.manualMark", "mail.compose.show_attachment_pane"}
|
|
|
|
func repoFile(t *testing.T, rel string) []byte {
|
|
t.Helper()
|
|
raw, err := os.ReadFile(filepath.Join("..", "..", rel))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return raw
|
|
}
|
|
|
|
// withDefaults places the module's defaults file as the push does.
|
|
func (f *fake) withDefaults() {
|
|
f.t.Helper()
|
|
f.write(meshDefaults, string(repoFile(f.t, "install/defaults-pref/mesh-defaults.js")))
|
|
}
|
|
|
|
// omni places a packed application holding the given files.
|
|
func (f *fake) omni(files map[string]string) {
|
|
f.t.Helper()
|
|
full := filepath.Join(f.Root, omniJa)
|
|
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
|
|
f.t.Fatal(err)
|
|
}
|
|
out, err := os.Create(full)
|
|
if err != nil {
|
|
f.t.Fatal(err)
|
|
}
|
|
w := zip.NewWriter(out)
|
|
for name, body := range files {
|
|
e, _ := w.Create(name)
|
|
e.Write([]byte(body))
|
|
}
|
|
w.Close()
|
|
out.Close()
|
|
}
|
|
|
|
// betterbirdsOwn is a small omni.ja: two files that contradict each other, as all-thunderbird.js and
|
|
// mailnews.js do in the pinned release.
|
|
func (f *fake) betterbirdsOwn() {
|
|
f.omni(map[string]string{
|
|
"greprefs.js": `pref("gfx.webrender.software", false);` + "\n",
|
|
"defaults/pref/all-thunderbird.js": `pref("offline.autoDetect", true);` + "\n" +
|
|
`pref("mail.threadpane.listview", 1);` + "\n" + `pref("mail.uidensity", 1);` + "\n" +
|
|
`pref("mail.shell.checkDefaultClient", true);` + "\n",
|
|
"defaults/pref/mailnews.js": `pref("offline.autoDetect", false);` + "\n" +
|
|
`pref("mail.biff.show_alert", true); // a comment after it` + "\n" + `pref("mail.spam.manualMark", false);` + "\n" +
|
|
`pref("mailnews.start_page.enabled", true);` + "\n",
|
|
"defaults/pref/calendar.js": `pref("calendar.alarms.showmissed", false);` + "\n",
|
|
"chrome/other.js": `pref("not.a.default", 1);` + "\n",
|
|
})
|
|
}
|
|
|
|
func stopBetterbird(t *testing.T, f *fake) {
|
|
t.Helper()
|
|
for _, pid := range []string{"4000", "4001"} {
|
|
if err := os.RemoveAll(filepath.Join(f.Root, "proc", pid)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheModuleShipsTheApprovedLocksAndDefaults(t *testing.T) {
|
|
m, _ := readManifest(t)
|
|
if a := artifact(t, m, "defaults"); a["from"] != "install/defaults-pref" {
|
|
t.Fatalf("the defaults file ships through the defaults artifact: %v", a)
|
|
}
|
|
if r := resource(t, m, "spelling"); r["path"] != prefDir || r["artifact"] != "defaults" {
|
|
t.Fatalf("placed in Betterbird's own defaults directory: %v", r)
|
|
}
|
|
raw := repoFile(t, "install/distribution/policies.json")
|
|
if why := policyProblem(raw); why != "" {
|
|
t.Fatal(why)
|
|
}
|
|
var p struct {
|
|
Policies map[string]json.RawMessage `json:"policies"`
|
|
}
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for k, want := range map[string]string{"DisableAppUpdate": "true", "DisableTelemetry": "true", "ExtensionUpdate": "false"} {
|
|
if string(p.Policies[k]) != want {
|
|
t.Errorf("%s is %s, not %s", k, p.Policies[k], want)
|
|
}
|
|
}
|
|
prefs := policyPrefsOf(raw)
|
|
for _, n := range []string{"mail.shell.checkDefaultClient", "mail.inappnotifications.enabled", "mailnews.start_page.enabled"} {
|
|
if prefs[n] != (policyPref{Value: "false", Locked: true}) {
|
|
t.Errorf("%s is locked off: %+v", n, prefs[n])
|
|
}
|
|
}
|
|
// Betterbird's Preferences policy accepts only these prefixes (Policies.sys.mjs); privacy. and toolkit. are not.
|
|
allowed := []string{"accessibility.", "app.update.", "browser.", "calendar.", "chat.", "datareporting.policy.", "dom.",
|
|
"extensions.", "general.autoScroll", "general.smoothScroll", "geo.", "gfx.", "intl.", "layers.", "layout.", "mail.",
|
|
"mailnews.", "media.", "network.", "pdfjs.", "places.", "print.", "signon.", "spellchecker.", "ui.", "widget."}
|
|
for n := range prefs {
|
|
ok := false
|
|
for _, a := range allowed {
|
|
ok = ok || strings.HasPrefix(n, a)
|
|
}
|
|
if !ok {
|
|
t.Errorf("the policy cannot lock %s: its prefix is not one Betterbird accepts", n)
|
|
}
|
|
}
|
|
|
|
text := string(repoFile(t, "install/defaults-pref/mesh-defaults.js"))
|
|
sh := parseShipped([]byte(text))
|
|
if len(sh.values) != 0 {
|
|
t.Errorf("the module's defaults hold no value of the operator's: they are the setting prefs (issue 586): %v", sh.values)
|
|
}
|
|
for n := range shippedDefaultsWant {
|
|
if strings.Contains(text, n) {
|
|
t.Errorf("%s is the operator's choice and lives in the setting prefs, never in the catalogue", n)
|
|
}
|
|
}
|
|
var keeps []string
|
|
for _, n := range sh.order {
|
|
if sh.keeps[n] {
|
|
keeps = append(keeps, n)
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(keeps, shippedKeepsWant) {
|
|
t.Errorf("the preferences kept at Betterbird's default: %v", keeps)
|
|
}
|
|
lines := strings.Split(text, "\n")
|
|
for i, l := range lines {
|
|
if defaultLine.MatchString(l) && (i == 0 || !strings.HasPrefix(lines[i-1], "// ")) {
|
|
t.Errorf("line %d has no comment saying why: %s", i+1, l)
|
|
}
|
|
if t2 := strings.TrimSpace(l); t2 != "" && !strings.HasPrefix(t2, "//") && !defaultLine.MatchString(l) {
|
|
t.Errorf("line %d is neither a comment nor a pref(…) line: %s", i+1, l)
|
|
}
|
|
}
|
|
for n := range sh.values {
|
|
if withheld(n) != "" || passwordSecret.MatchString(n) {
|
|
t.Errorf("%s is an account's or a credential's preference, never a default of the module", n)
|
|
}
|
|
if _, locked := prefs[n]; locked {
|
|
t.Errorf("%s is both locked and a default", n)
|
|
}
|
|
}
|
|
}
|
|
|
|
// T2: Betterbird's own defaults are values read from omni.ja, in Gecko's order, and the module's beside them.
|
|
func TestPrefsSayTheDefaultAndTheFileThatGivesIt(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
f.placedByTheMesh()
|
|
f.withDefaults()
|
|
f.betterbirdsOwn()
|
|
links(t, nil)
|
|
got, err := f.Prefs([]string{"offline.autoDetect", "mail.threadpane.listview", "mail.uidensity", "gfx.webrender.software",
|
|
"mailnews.start_page.enabled", "layout.css.always_underline_links", "not.a.default"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
by := map[string]Pref{}
|
|
for _, p := range got["prefs"].([]Pref) {
|
|
by[p.Name] = p
|
|
}
|
|
if p := by["offline.autoDetect"]; p.Value != "true" || p.Default != "true" || !strings.Contains(p.DefaultFrom, "all-thunderbird.js") {
|
|
t.Errorf("of two files, the one first by name holds, as Gecko reads them: %+v", p)
|
|
}
|
|
if p := by["mail.threadpane.listview"]; p.Value != "1" || strings.Contains(p.From, "module") || strings.Contains(p.DefaultFrom, "module") {
|
|
t.Errorf("the module ships no value of the operator's, so Betterbird's own default holds: %+v", p)
|
|
}
|
|
if p := by["gfx.webrender.software"]; p.Value != "false" || !strings.Contains(p.From, "greprefs.js") {
|
|
t.Errorf("%+v", p)
|
|
}
|
|
if p := by["mailnews.start_page.enabled"]; p.Value != "false" || p.From != "locked by the module's policy" || p.Default != "true" {
|
|
t.Errorf("a locked preference says its lock and Betterbird's default under it: %+v", p)
|
|
}
|
|
if p := by["layout.css.always_underline_links"]; p.Value != "" || !strings.Contains(p.From, "built-in") {
|
|
t.Errorf("a preference in no file is said as such: %+v", p)
|
|
}
|
|
if p := by["not.a.default"]; p.Default != "" {
|
|
t.Errorf("only defaults/pref and greprefs.js are defaults: %+v", p)
|
|
}
|
|
}
|
|
|
|
// T4: of mailnews.* only what names an account, a server or the operator's tags is withheld.
|
|
func TestMailnewsIsWithheldOnlyWhereItNamesAnAccount(t *testing.T) {
|
|
for name, held := range map[string]bool{
|
|
"mailnews.start_page.enabled": false,
|
|
"mailnews.default_view_flags": false,
|
|
"mailnews.message_display.disable_remote_image": false,
|
|
"mailnews.tags.$label1.tag": true,
|
|
"mailnews.smtp.loglevel": true,
|
|
"mailnews.reply_to_self_check_all_ident": true,
|
|
"mailnews.auto_config.fetchFromISP.sendEmailAddress": true,
|
|
"mailnews.oauth.useSchemeRedirect": true,
|
|
"mail.server.server1.hostname": true,
|
|
} {
|
|
if got := withheld(name) != ""; got != held {
|
|
t.Errorf("%s: withheld %v, want %v", name, got, held)
|
|
}
|
|
}
|
|
}
|
|
|
|
// T1: every managed preference through the layers, and drift: a declared value the profile's user.js does not hold
|
|
// yet, or a value the profile saved where the module keeps Betterbird's default.
|
|
func TestTheConfigShowsTheLayersAndTheDrift(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
f.placedByTheMesh()
|
|
f.withDefaults()
|
|
f.betterbirdsOwn()
|
|
links(t, nil)
|
|
f.declare(`user_pref("mail.uifontsize", 16); user_pref("mail.biff.show_alert", false); user_pref("calendar.alarms.showmissed", true);`)
|
|
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("mail.uifontsize", 16);`+"\n"+`user_pref("calendar.alarms.showmissed", true);`+"\n")
|
|
f.write(profileDir+"/prefs.js", "// Mozilla User Preferences\n"+
|
|
`user_pref("extensions.activeThemeID", "thunderbird-compact-dark@mozilla.org");`+"\n"+
|
|
`user_pref("mail.uifontsize", 12);`+"\n"+
|
|
`user_pref("mail.biff.show_alert", true);`+"\n"+
|
|
`user_pref("mail.spam.manualMark", true);`+"\n"+
|
|
`user_pref("browser.display.use_system_colors", false);`+"\n"+
|
|
`user_pref("mailnews.start_page.enabled", true);`+"\n")
|
|
c, err := f.Config(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
by := map[string]ConfigPref{}
|
|
for _, p := range c.Prefs {
|
|
by[p.Name] = p
|
|
}
|
|
if len(c.Prefs) != 3+3+len(shippedKeepsWant) || !strings.HasPrefix(c.Defaults, "placed") {
|
|
t.Fatalf("every lock, declared preference and kept default: %d, %s", len(c.Prefs), c.Defaults)
|
|
}
|
|
if !reflect.DeepEqual(c.Drifting, []string{"extensions.activeThemeID", "mail.spam.manualMark", "mail.biff.show_alert"}) {
|
|
t.Fatalf("drift: %v", c.Drifting)
|
|
}
|
|
if p := by["mail.biff.show_alert"]; p.Layer != "declared" || p.Declared != "false" || p.Profile != "true" ||
|
|
p.Effective != "true" || !strings.Contains(p.Drift, "the setting prefs says false") || !strings.Contains(p.Remedy, "betterbird_user_js") {
|
|
t.Errorf("a declared value the profile's user.js does not hold yet: %+v", p)
|
|
}
|
|
if p := by["mail.spam.manualMark"]; p.Layer != "Betterbird's default" || p.BetterbirdDefault != "false" ||
|
|
!strings.Contains(p.Drift, "beats the module's false") || p.Remedy != "" {
|
|
t.Errorf("a saved value where the module keeps Betterbird's default: %+v", p)
|
|
}
|
|
if p := by["extensions.activeThemeID"]; p.Layer != "Betterbird's default" || !strings.Contains(p.Drift, `"default-theme@mozilla.org"`) ||
|
|
!strings.Contains(p.Remedy, "add-on manager") || p.Profile != `"thunderbird-compact-dark@mozilla.org"` {
|
|
t.Errorf("another theme is drift whose remedy is the add-on manager, its id answered whole: %+v", p)
|
|
}
|
|
if p := by["browser.display.use_system_colors"]; p.Drift != "" {
|
|
t.Errorf("a saved value equal to Betterbird's default is not drift: %+v", p)
|
|
}
|
|
if p := by["mail.uifontsize"]; p.Drift != "" || p.Declared != "16" || p.Effective != "16" || !strings.Contains(p.From, "user.js") {
|
|
t.Errorf("a declared value the user.js holds wins at the start, whatever prefs.js saved: %+v", p)
|
|
}
|
|
if p := by["mailnews.start_page.enabled"]; p.Layer != "locked" || p.Effective != "false" || p.Drift != "" {
|
|
t.Errorf("a lock wins over the profile: %+v", p)
|
|
}
|
|
if p := by["calendar.alarms.showmissed"]; p.Effective != "true" || p.Layer != "declared" {
|
|
t.Errorf("%+v", p)
|
|
}
|
|
// T5: the check says the drift, with both remedies.
|
|
ch, err := f.Check()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
for _, x := range ch.Findings {
|
|
found = found || (strings.Contains(x.What, "1 managed preference(s) drift") && strings.Contains(x.What, "mail.spam.manualMark") &&
|
|
strings.Contains(x.Do, "betterbird_config_apply") && strings.Contains(x.Do, "keep"))
|
|
}
|
|
theme := false
|
|
for _, x := range ch.Findings {
|
|
theme = theme || (strings.HasPrefix(x.What, "extensions.activeThemeID drifts") && strings.Contains(x.Do, "add-on manager") &&
|
|
!strings.Contains(x.Do, "betterbird_config_apply"))
|
|
}
|
|
declared := false
|
|
for _, x := range ch.Findings {
|
|
declared = declared || (strings.HasPrefix(x.What, "mail.biff.show_alert drifts") && strings.Contains(x.Do, "betterbird_user_js"))
|
|
}
|
|
if !found || !theme || !declared {
|
|
t.Fatalf("the check reports the drift: %+v", ch.Findings)
|
|
}
|
|
os.Remove(filepath.Join(f.Root, meshDefaults))
|
|
ch, _ = f.Check()
|
|
found = false
|
|
for _, x := range ch.Findings {
|
|
found = found || strings.Contains(x.What, "the module's defaults are not placed")
|
|
}
|
|
if !found {
|
|
t.Fatalf("defaults missing on a node of the mesh's release is a finding: %+v", ch.Findings)
|
|
}
|
|
}
|
|
|
|
// T3: the drifting values are cleared from prefs.js only while Betterbird is not running, and nothing else.
|
|
func TestConfigApplyClearsTheDriftOnlyWhileBetterbirdIsQuit(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
f.placedByTheMesh()
|
|
f.withDefaults()
|
|
f.betterbirdsOwn()
|
|
links(t, nil)
|
|
f.declare("// none")
|
|
prefs := "// Mozilla User Preferences\n" +
|
|
`user_pref("extensions.activeThemeID", "thunderbird-compact-dark@mozilla.org");` + "\n" +
|
|
`user_pref("mail.identity.id1.useremail", "someone@example.org");` + "\n" +
|
|
`user_pref("mail.spam.manualMark", true);` + "\n" +
|
|
`user_pref("mail.uidensity", 0);` + "\n"
|
|
f.write(profileDir+"/prefs.js", prefs)
|
|
dry, err := f.ConfigApply(false)
|
|
if err != nil || dry.Applied || len(dry.Clears) != 1 || len(dry.NotCleared) != 1 {
|
|
t.Fatalf("a dry run lists and changes nothing: %+v %v", dry, err)
|
|
}
|
|
if f.read(profileDir+"/prefs.js") != prefs {
|
|
t.Fatal("a dry run wrote prefs.js")
|
|
}
|
|
if _, err := f.ConfigApply(true); err == nil || !strings.Contains(err.Error(), "running") {
|
|
t.Fatalf("refused while Betterbird runs: %v", err)
|
|
}
|
|
if f.read(profileDir+"/prefs.js") != prefs {
|
|
t.Fatal("prefs.js changed while Betterbird runs")
|
|
}
|
|
stopBetterbird(t, f)
|
|
f.write(profileDir+"/lock", "")
|
|
if _, err := f.ConfigApply(true); err == nil || !strings.Contains(err.Error(), "locked") {
|
|
t.Fatalf("refused while the profile's own lock is there: %v", err)
|
|
}
|
|
if err := os.Remove(filepath.Join(f.Root, profileDir, "lock")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a, err := f.ConfigApply(true)
|
|
if err != nil || !a.Applied {
|
|
t.Fatalf("%+v %v", a, err)
|
|
}
|
|
want := "// Mozilla User Preferences\n" +
|
|
`user_pref("extensions.activeThemeID", "thunderbird-compact-dark@mozilla.org");` + "\n" +
|
|
`user_pref("mail.identity.id1.useremail", "someone@example.org");` + "\n" +
|
|
`user_pref("mail.uidensity", 0);` + "\n"
|
|
if got := f.read(profileDir + "/prefs.js"); got != want {
|
|
t.Fatalf("only the drifting lines go:\n%s", got)
|
|
}
|
|
said := map[string]Cleared{}
|
|
for _, c := range a.Clears {
|
|
said[c.Name] = c
|
|
}
|
|
if c := said["mail.spam.manualMark"]; c.Was != "true" || c.Now != "false" {
|
|
t.Errorf("it says what it cleared and what holds now: %+v", c)
|
|
}
|
|
if _, cleared := said["extensions.activeThemeID"]; cleared || len(a.NotCleared) != 1 || !strings.Contains(a.NotCleared[0], "add-on manager") {
|
|
t.Errorf("the theme is never cleared, and said with its remedy: %+v", a)
|
|
}
|
|
raw, _ := json.Marshal(a)
|
|
if strings.Contains(string(raw), "example.org") {
|
|
t.Fatalf("no account value leaves: %s", raw)
|
|
}
|
|
if c, _ := f.Config(nil); !reflect.DeepEqual(c.Drifting, []string{"extensions.activeThemeID"}) {
|
|
t.Fatalf("once applied, only the theme drifts, until the add-on manager changes it: %v", c.Drifting)
|
|
}
|
|
os.Remove(filepath.Join(f.Root, meshDefaults))
|
|
if _, err := f.ConfigApply(true); err == nil || !strings.Contains(err.Error(), "not placed") {
|
|
t.Fatalf("without the module's defaults there is nothing to come back to: %v", err)
|
|
}
|
|
}
|
|
|
|
// T6: keeping a value on one node is that node's layer of the setting prefs proposed through the controller, one
|
|
// entry per preference beside what the layer holds (issue 586), never a write to user.js.
|
|
func TestKeepProposesANodeLayerThroughTheController(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
f.placedByTheMesh()
|
|
f.withDefaults()
|
|
f.betterbirdsOwn()
|
|
links(t, nil)
|
|
f.declare(`user_pref("calendar.alarms.playsound", false); user_pref("mail.uifontsize", 12);`)
|
|
f.write(profileDir+"/prefs.js", `user_pref("mail.uifontsize", 16);`+"\n"+`user_pref("mail.spam.manualMark", true);`+"\n")
|
|
oldNode, oldAsk := nodeName, asker
|
|
t.Cleanup(func() { nodeName, asker = oldNode, oldAsk })
|
|
nodeName = func() string { return "shanks" }
|
|
var asked []map[string]any
|
|
asker = func(v string, a any) (json.RawMessage, error) {
|
|
args := a.(map[string]any)
|
|
asked = append(asked, args)
|
|
if args["propose"] == nil {
|
|
return json.RawMessage(`{"ok": true, "output": "{\"prefs\": {\"calendar.alarms.playsound\": false}, \"identities\": {\"mail\": {\"account\": \"mail\"}}}\n set at the terminal\nbetterbird on shanks, every value"}`), nil
|
|
}
|
|
return json.RawMessage(`{"ok": true, "output": "proposed: p-1"}`), nil
|
|
}
|
|
before := f.read(testHome + "/" + prefsFile)
|
|
c, err := f.Config([]string{"mail.uifontsize", "mail.spam.manualMark"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(asked) != 2 || asked[0]["node"] != "shanks" || asked[1]["node"] != "shanks" || asked[1]["propose"] != "true" ||
|
|
asked[1]["module"] != "betterbird" {
|
|
t.Fatalf("the node's layer read, then proposed: %v", asked)
|
|
}
|
|
var values map[string]any
|
|
if err := json.Unmarshal([]byte(asked[1]["values"].(string)), &values); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := map[string]any{"calendar.alarms.playsound": false, "mail.uifontsize": float64(16), "mail.spam.manualMark": true}
|
|
if !reflect.DeepEqual(values["prefs"], want) || values["identities"] == nil {
|
|
t.Fatalf("the layer as it stands with the kept values, one entry each:\n got %v\nwant %v", values, want)
|
|
}
|
|
if c.Proposed != "proposed: p-1" || f.read(testHome+"/"+prefsFile) != before {
|
|
t.Fatalf("proposed, and nothing written: %+v", c)
|
|
}
|
|
for _, bad := range [][]string{{"mailnews.start_page.enabled"}, {"some.other.pref"}, {"calendar.alarms.playsound"}} {
|
|
asked = nil
|
|
if _, err := f.Config(bad); err == nil || len(asked) != 0 {
|
|
t.Errorf("%v: a lock, an unmanaged name or a value the profile did not save is refused: %v", bad, err)
|
|
}
|
|
}
|
|
nodeName = func() string { return "" }
|
|
if _, err := f.Config([]string{"mail.uifontsize"}); err == nil || !strings.Contains(err.Error(), "MESH_NODE") {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
}
|
|
|
|
// The review of pull request 253: whether Betterbird asks to be the default client is decided from the policy's
|
|
// own Preferences entry, never from what else the policy lacks.
|
|
func TestTheDefaultClientAskIsDecidedFromThePreferencesEntry(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
f.placedByTheMesh()
|
|
links(t, nil)
|
|
f.write(policyFile, `{"policies": {"ExtensionSettings": {}}}`)
|
|
if why := f.defaultClientAsk(filepath.Join(f.Root, profileDir)); why == "" {
|
|
t.Fatal("a policy lacking the updater and the lock still asks")
|
|
}
|
|
f.write(policyFile, `{"policies": {"Preferences": {"mail.shell.checkDefaultClient": {"Value": false, "Status": "locked"}}}}`)
|
|
if why := f.defaultClientAsk(filepath.Join(f.Root, profileDir)); why != "" {
|
|
t.Fatalf("the lock holds whatever else the policy lacks: %s", why)
|
|
}
|
|
}
|
|
|
|
// The review of pull request 256: the theme is the add-on database's, and a saved default is not drift.
|
|
func TestTheThemeIsTheAddOnDatabasesAndASavedDefaultIsNotDrift(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
f.placedByTheMesh()
|
|
f.withDefaults()
|
|
links(t, nil)
|
|
f.write(profileDir+"/prefs.js", `user_pref("browser.display.use_system_colors", false);`+"\n"+
|
|
`user_pref("layout.css.always_underline_links", false);`+"\n")
|
|
f.write(profileDir+"/extensions.json", `{"addons":[{"id":"thunderbird-compact-dark@mozilla.org","type":"theme","active":true,"location":"app-builtin"},`+
|
|
`{"id":"default-theme@mozilla.org","type":"theme","active":false,"location":"app-builtin"}]}`)
|
|
c, err := f.Config(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(c.Drifting, []string{"extensions.activeThemeID"}) {
|
|
t.Fatalf("the active theme from the add-on database, with no preference saved, is drift; a saved default is not: %v", c.Drifting)
|
|
}
|
|
f.write(profileDir+"/prefs.js", `user_pref("extensions.activeThemeID", "default-theme@mozilla.org");`+"\n")
|
|
f.write(profileDir+"/extensions.json", `{"addons":[{"id":"default-theme@mozilla.org","type":"theme","active":true,"location":"app-builtin"}]}`)
|
|
if c, _ := f.Config(nil); len(c.Drifting) != 0 {
|
|
t.Fatalf("a profile back on the default theme does not drift: %v", c.Drifting)
|
|
}
|
|
if _, err := f.Config([]string{"extensions.activeThemeID"}); err == nil || !strings.Contains(err.Error(), "add-on manager") {
|
|
t.Fatalf("a node layer cannot choose the theme: %v", err)
|
|
}
|
|
}
|
|
|
|
// The review of pull request 256: a credential's or an account's name in the defaults file is never managed.
|
|
func TestAnAccountsNameInTheDefaultsIsNeverManaged(t *testing.T) {
|
|
f := newFake(t)
|
|
f.betterbird()
|
|
f.placedByTheMesh()
|
|
links(t, nil)
|
|
f.write(meshDefaults, string(repoFile(t, "install/defaults-pref/mesh-defaults.js"))+
|
|
`pref("mail.identity.id1.useremail", "x");`+"\n"+"// keeps Betterbird's default: mail.server.server1.hostname\n"+
|
|
`pref("mail.smtpservers", "");`+"\n")
|
|
prefs := `user_pref("mail.identity.id1.useremail", "someone@example.org");` + "\n" +
|
|
`user_pref("mail.server.server1.hostname", "imap.example.org");` + "\n" +
|
|
`user_pref("mail.smtpservers", "smtp1");` + "\n"
|
|
f.write(profileDir+"/prefs.js", prefs)
|
|
c, err := f.Config(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(c.Refused) != 3 || len(c.Drifting) != 0 {
|
|
t.Fatalf("refused, never managed: %+v", c)
|
|
}
|
|
raw, _ := json.Marshal(c)
|
|
if strings.Contains(string(raw), "example.org") {
|
|
t.Fatalf("no account value is answered: %s", raw)
|
|
}
|
|
stopBetterbird(t, f)
|
|
if _, err := f.ConfigApply(true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if f.read(profileDir+"/prefs.js") != prefs {
|
|
t.Fatal("prefs.js keeps every account's value")
|
|
}
|
|
ch, _ := f.Check()
|
|
found := false
|
|
for _, x := range ch.Findings {
|
|
found = found || (strings.Contains(x.What, "3 preference(s) the tools never manage") && strings.Contains(x.Do, "mesh-defaults.js"))
|
|
}
|
|
if !found {
|
|
t.Fatalf("the check says so: %+v", ch.Findings)
|
|
}
|
|
}
|