package settings import ( "log" "os" "path/filepath" "strings" "testing" ) func newSMBTestSettings(t *testing.T) (*Settings, string) { t.Helper() dir := t.TempDir() path := filepath.Join(dir, "settings.json") s, err := Load(path, log.New(os.Stderr, "", 0)) if err != nil { t.Fatalf("Load: %v", err) } return s, path } func TestValidateSMBServerName(t *testing.T) { good := []string{"FELHOM", "Otthon-1", "box_2", "A", "sixteencharnam"} // last = 14 chars for _, n := range good { if err := ValidateSMBServerName(n); err != nil { t.Errorf("%q should be valid: %v", n, err) } } bad := []string{"", " ", "sixteencharname1", "has space", "bad/slash", "-lead", "trail-", "dot.name", "back\\slash"} for _, n := range bad { if err := ValidateSMBServerName(n); err == nil { t.Errorf("%q should be invalid", n) } } } func TestValidateSMBShareName(t *testing.T) { good := []string{"dokumentumok", "filmek", "csalad_kepek", "A1"} for _, n := range good { if err := ValidateSMBShareName(n); err != nil { t.Errorf("%q should be valid: %v", n, err) } } // Scenario C: "../x", >15-char, and invalid NetBIOS charset must all refuse. bad := []string{"", "../x", "a/b", "a\\b", "file.txt", "way-too-long-nam", "has space", "-x", "x-"} for _, n := range bad { if err := ValidateSMBShareName(n); err == nil { t.Errorf("%q should be invalid", n) } } } func TestSMBShareCRUD(t *testing.T) { s, path := newSMBTestSettings(t) sh := SMBShare{Name: "dokumentumok", Path: "/mnt/felhom-drives/scratch1/shares/dokumentumok", Offsite: true} if err := s.AddSMBShare(sh); err != nil { t.Fatalf("AddSMBShare: %v", err) } got := s.GetSMBShares() if len(got) != 1 || got[0].Name != "dokumentumok" || !got[0].Offsite { t.Fatalf("share not stored as expected: %+v", got) } if got[0].CreatedAt == "" { t.Error("CreatedAt not stamped on add") } // Case-insensitive collision is refused AND does not mutate the registry (Scenario C non-effect). if err := s.AddSMBShare(SMBShare{Name: "DOKUMENTUMOK", Path: "/other"}); err == nil { t.Error("case-insensitive name collision should be refused") } if len(s.GetSMBShares()) != 1 { t.Error("refused collision must not mutate the registry") } // Offsite (Felhőmentés) toggle persists. if err := s.SetSMBShareOffsite("dokumentumok", false); err != nil { t.Fatalf("SetSMBShareOffsite: %v", err) } if s.GetSMBShares()[0].Offsite { t.Error("offsite toggle did not persist") } // Persistence across reload. s2, err := Load(path, log.New(os.Stderr, "", 0)) if err != nil { t.Fatalf("reload: %v", err) } if r := s2.GetSMBShares(); len(r) != 1 || r[0].Name != "dokumentumok" { t.Errorf("share did not persist across reload: %+v", r) } // Remove is config-only + errors on a missing name. if err := s.RemoveSMBShare("dokumentumok"); err != nil { t.Fatalf("RemoveSMBShare: %v", err) } if len(s.GetSMBShares()) != 0 { t.Error("share not removed") } if err := s.RemoveSMBShare("nope"); err == nil { t.Error("removing a missing share should error") } } func TestSMBSettingsDefaultsAndNoPassword(t *testing.T) { s, path := newSMBTestSettings(t) // Unconfigured → disabled with the default server name (never nil). def := s.GetSMBSettings() if def.Enabled || def.ServerName != DefaultSMBServerName { t.Errorf("default SMB settings wrong: %+v", def) } if err := s.SetSMBEnabled(true); err != nil { t.Fatal(err) } if err := s.SetSMBServerName("OTTHON"); err != nil { t.Fatal(err) } if err := s.SetSMBUserSet(true); err != nil { t.Fatal(err) } cur := s.GetSMBSettings() if !cur.Enabled || cur.ServerName != "OTTHON" || !cur.UserSet { t.Errorf("SMB settings not stored: %+v", cur) } // The household SMB password must NEVER be persisted (rule 4). The struct has no field for it; // prove the on-disk file carries only the UserSet flag, no plaintext secret. raw, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if strings.Contains(string(raw), "password") && strings.Contains(string(raw), "smb") { // there is a claim_code_hash etc.; assert no *smb* password key specifically if strings.Contains(string(raw), "smb_password") || strings.Contains(string(raw), "\"password\": \"") { t.Errorf("settings.json appears to store an SMB password:\n%s", raw) } } }