mesh/merge-gate head f1f866050eee merged into trunk e209d46faf3d — pass: builds betterbird, sent nowhere; no bus step; 2 wait(s) for a person; every machin…
mesh/repo-check head f1f866050eee merged into trunk e209d46faf3d — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
The fix is the approved settings layer (proposal s09bf8c99da7ac8b9: calendar.alarms.showmissed false), pushed to g14 and shanks.
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)
|
|
}
|
|
}
|