package settings import ( "bytes" "crypto/rand" "encoding/json" "errors" "fmt" "io" "log" "maps" "os" "strings" "testing" "time" ) var ( AdditionalKeyValuePairs = map[string]string{ "D": "d", "E": "e", "F": "f", } ) func TestSettings(t *testing.T) { err := os.Remove(TheTestFilename) if err != nil && !errors.Is(err, os.ErrNotExist) { t.Fatalf("error cleaning up %s from previous run: %v\n", TheTestFilename, err) } var buffer bytes.Buffer err = WriteMapToWriter(&buffer, KeyValuePairs) if err != nil { t.Fatalf("error writing test data to *bytes.Buffer: %v\n", err) } err = os.WriteFile(TheTestFilename, buffer.Bytes(), 0664) if err != nil { t.Fatalf("error writing %s: %v\n", TheTestFilename, err) } defer func() { if err := os.Remove(TheTestFilename); err != nil { t.Fatalf("error cleaning up %s from this run: %v\n", TheTestFilename, err) } }() settings := TheSettings err = settings.Update() if err != nil { t.Fatalf("error updating settings: %v\n", err) } else if want, got := TheTestFilename, settings.GetFilename(); want != got { t.Fatalf("error: wanted '%s'; got '%s'\n", want, got) } else if got := settings.modtime; got.Equal(time.Time{}) { t.Fatalf("error: modtime == (time.Time{}) - not updated when initialized") } else { for k, v := range KeyValuePairs { if !settings.ContainsKey(k) { t.Fatalf("error: key '%s' not found\n", k) } else if want, got := v, settings.GetKeyValue(k); want != got { t.Fatalf("error: wanted '%s' for key '%s'; got '%s'\n", want, k, got) } } } // change the filename TheNewTestFilename := fmt.Sprintf("New%s", TheTestFilename) buffer.Reset() err = WriteMapToWriter(&buffer, AdditionalKeyValuePairs) if err != nil { t.Fatalf("error writing new map to *bytes.Buffer: %v\n", err) } err = os.WriteFile(TheNewTestFilename, buffer.Bytes(), 0664) if err != nil { t.Fatalf("error writing new test file %s: %v\n", TheNewTestFilename, err) } defer func() { if err := os.Remove(TheNewTestFilename); err != nil { t.Fatalf("error deleting %s: %v", TheNewTestFilename, err) } }() if updated := settings.SetFilename(TheNewTestFilename); !updated { t.Fatalf("error: SetFilename() indicated the filename was not updated...\n") } else if updated := settings.SetFilename(settings.GetFilename()); updated { t.Fatalf("error: setting filename to itself still resulted in updated=true\n") } for k, v := range AdditionalKeyValuePairs { if !settings.ContainsKey(k) { t.Fatalf("error: key '%s' not found\n", k) } else if want, got := v, settings.GetKeyValue(k); want != got { t.Fatalf("error: wanted '%s' for key '%s'; got '%s'\n", want, k, got) } } // update the file, wait, then try again newKeyValuePairs := make(map[string]string) maps.Copy(newKeyValuePairs, KeyValuePairs) maps.Copy(newKeyValuePairs, AdditionalKeyValuePairs) newKeyValuePairs["G"] = "g" buffer.Reset() err = WriteMapToWriter(&buffer, newKeyValuePairs) if err != nil { t.Fatalf("error writing new key value pairs to *bytes.Buffer: %v\n", err) } err = os.WriteFile(TheNewTestFilename, buffer.Bytes(), 0664) if err != nil { t.Fatalf("error writing updates to %s: %v\n", TheNewTestFilename, err) } select { case <-t.Context().Done(): t.Fatalf("error: timed out before finishing wait") case <-time.After(time.Duration(float64(MaintenanceRoutinePace.Nanoseconds()) * 1.1)): } for k, v := range newKeyValuePairs { if !settings.ContainsKey(k) { t.Fatalf("error: key '%s' not found in settings\n", k) } else if want, got := v, settings.GetKeyValue(k); want != got { t.Fatalf("error: wanted '%s' for key '%s'; got '%s'\n", want, k, got) } } buffer.Reset() defaultLogWriter := log.Writer() log.SetOutput(&buffer) // set settings to a fake filename WarnIfFileNotFound = true if updated := settings.SetFilename("fakefilename.txt"); !updated { t.Fatalf("failed to update filename to fake filename...\n") } log.SetOutput(defaultLogWriter) WarnIfFileNotFound = false if !strings.Contains(buffer.String(), "no such file or directory") { t.Fatalf("error: wanted error indicating 'no such file or directory'; got %v\n", err) } } func TestDump(t *testing.T) { dump, err := TheSettings.Dump() if err != nil { t.Fatalf("error dumping settings: %v\n", err) } scanner := NewScanner(bytes.NewReader(dump)) var found int loop: for { key, value, err := scanner.ReadNextLine() if err != nil { if !errors.Is(err, io.EOF) { t.Fatalf("error reading next line: %v\n", err) } break loop } if want, got := TheSettings.GetKeyValue(key), value; want != got { t.Fatalf("error: wanted key %q value %q; got %q\n", key, want, got) } found++ } if want, got := len(TheSettings.contents), found; want != got { t.Fatalf("error: wanted %d entries; got %d\n", want, got) } } func TestDumpJson(t *testing.T) { marshaled, err := TheSettings.DumpToJson() if err != nil { t.Fatalf("error dumping to json: %v\n", err) } var unmarshaled map[string]string err = json.Unmarshal(marshaled, &unmarshaled) if err != nil { t.Fatalf("error unmarshaling json dump: %v\n", err) } if want, got := TheSettings.contents, unmarshaled; !maps.Equal(want, got) { t.Fatalf("error: wanted %v; got %v\n", want, got) } } func TestSetKeyValue(t *testing.T) { key, value := rand.Text(), rand.Text() if !TheSettings.SetKeyValue(key, value) { t.Fatalf("SetKeyValue didn't update?\n") } if want, got := value, TheSettings.GetKeyValue(key); want != got { t.Fatalf("error: wanted %q; got %q\n", want, got) } } func TestParseBool(t *testing.T) { TheTestItems := map[string]bool{ "yes": true, "on": true, "true": true, "True": true, "TRUE": true, "no": false, "off": false, "false": false, } for boolString, want := range TheTestItems { got, err := ParseBool(boolString) if err != nil { t.Fatalf("error: %v\n", err) } if want != got { t.Fatalf("error: wanted %s=%v; got %v\n", boolString, want, got) } } TheBadTestItems := []string{ "blue", "cat", "https://google.com", } for _, boolString := range TheBadTestItems { got, err := ParseBool(boolString) if err == nil { t.Fatalf("error: successfully parsed %s as bool %v...\n", boolString, got) } } } func TestGetStringOrDefault(t *testing.T) { dump, err := TheSettings.Dump() if err != nil { t.Fatalf("error getting dump: %v\n", err) } scanner := NewScanner(bytes.NewReader(dump)) loop: for { key, value, err := scanner.ReadNextLine() if err != nil { if !errors.Is(err, io.EOF) { t.Fatalf("error reading from dump: %v\n", err) } break loop } if want, got := value, TheSettings.GetStringOrDefault(key, "SomeInvalidDefault"); want != got { t.Fatalf("error: wanted key %q=%q; got %q\n", key, want, got) } } badKey := "some new key" badValue := "some invalid value" if want, got := badValue, TheSettings.GetStringOrDefault(badKey, badValue); want != got { t.Fatalf("error: wanted key %q=%q; got %q\n", badKey, want, got) } } func TestGetBoolOrDefault(t *testing.T) { key, value := "some bool value", "on" TheSettings.SetKeyValue(key, value) if want, got := true, TheSettings.GetBoolOrDefault(key, false); want != got { t.Fatalf("error: wanted %q=%v; got %v\n", key, want, got) } value = "shark" TheSettings.SetKeyValue(key, value) if want, got := false, TheSettings.GetBoolOrDefault(key, false); want != got { t.Fatalf("error: wanted %q=%v; got %v\n", key, want, got) } } func TestGetDurationOrDefault(t *testing.T) { key, value := "some duration value", "10s" TheSettings.SetKeyValue(key, value) if want, got := time.Second*10, TheSettings.GetDurationOrDefault(key, time.Minute*10); want != got { t.Fatalf("error: wanted %q=%s; got %s\n", key, want, got) } value = "loud" TheSettings.SetKeyValue(key, value) if want, got := time.Minute*10, TheSettings.GetDurationOrDefault(key, time.Minute*10); want != got { t.Fatalf("error: wanted %q=%s; got %s\n", key, want, got) } } func TestGetIntOrDefault(t *testing.T) { key, value := "some int value", "10" TheSettings.SetKeyValue(key, value) if want, got := 10, TheSettings.GetIntOrDefault(key, 200); want != got { t.Fatalf("error: wanted %q=%d; got %d\n", key, want, got) } value = "cat" TheSettings.SetKeyValue(key, value) if want, got := 200, TheSettings.GetIntOrDefault(key, 200); want != got { t.Fatalf("error: wanted default value %d for bad key; got %d\n", want, got) } if want, got := 200, TheSettings.GetIntOrDefault("some fake bad key", 200); want != got { t.Fatalf("error: wanted default value %d for bad key; got %d\n", want, got) } }