| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- package main
- import (
- "encoding/json"
- "os"
- "path/filepath"
- "strings"
- "testing"
- )
- // A .json argument is a path and everything else is a name, and that is the whole
- // rule. The suffix is matched without regard to case so it reads the same on
- // Linux, macOS and Windows, and no separator is looked for: the Windows file API
- // takes both, so a check for one would be a platform difference with no behaviour
- // behind it.
- func TestResolveProfileTargetTreatsAJsonSuffixAsAPath(t *testing.T) {
- userDir := t.TempDir()
- t.Cleanup(stubUserConfigDir(t, userDir))
- t.Cleanup(func() { profilesDirOverride = "" })
- user := filepath.Join(userDir, "qmk-rgb-tool", "profiles")
- for _, tc := range []struct {
- arg string
- wantPath string
- wantName string
- wantFile bool
- }{
- {"lala.json", "lala.json", "lala", true},
- {"./lala.json", "./lala.json", "lala", true},
- {"profiles/lava.json", "profiles/lava.json", "lava", true},
- {"LALA.JSON", "LALA.JSON", "LALA", true},
- {"lava", filepath.Join(user, "lava.json"), "lava", false},
- {"my profile", filepath.Join(user, "my-profile.json"), "my profile", false},
- } {
- path, name, isFile := resolveProfileTarget(tc.arg)
- if isFile != tc.wantFile {
- t.Errorf("resolveProfileTarget(%q) isPath = %v, want %v", tc.arg, isFile, tc.wantFile)
- }
- if path != tc.wantPath {
- t.Errorf("resolveProfileTarget(%q) path = %q, want %q", tc.arg, path, tc.wantPath)
- }
- if name != tc.wantName {
- t.Errorf("resolveProfileTarget(%q) name = %q, want %q", tc.arg, name, tc.wantName)
- }
- }
- }
- // The two forms read two different files, and the argument says which: a path
- // reads the file it names, a name reads the user's directory. The same fixture
- // holds a file of each, so a rule that guessed instead would have one answer for
- // both arguments.
- func TestLoadProfileReadsTheFileTheArgumentNames(t *testing.T) {
- userDir := t.TempDir()
- t.Cleanup(stubUserConfigDir(t, userDir))
- t.Cleanup(func() { profilesDirOverride = "" })
- here := t.TempDir()
- if err := os.MkdirAll(filepath.Join(here, "profiles"), 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(here, "profiles", "lava.json"),
- []byte(`{"name":"from-the-checkout","version":1,"zones":{}}`), 0o644); err != nil {
- t.Fatal(err)
- }
- dir := filepath.Join(userDir, "qmk-rgb-tool", "profiles")
- if err := os.MkdirAll(dir, 0o755); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(dir, "lava.json"),
- []byte(`{"name":"from-the-user-directory","version":1,"zones":{}}`), 0o644); err != nil {
- t.Fatal(err)
- }
- t.Chdir(here)
- path, err := LoadProfile("profiles/lava.json")
- if err != nil {
- t.Fatalf("LoadProfile(profiles/lava.json): %v", err)
- }
- if path.Name != "from-the-checkout" {
- t.Errorf("LoadProfile(profiles/lava.json).Name = %q, want the file it names", path.Name)
- }
- byName, err := LoadProfile("lava")
- if err != nil {
- t.Fatalf("LoadProfile(lava): %v", err)
- }
- if byName.Name != "from-the-user-directory" {
- t.Errorf("LoadProfile(lava).Name = %q, want the user's directory, not the checkout's", byName.Name)
- }
- }
- // A path that is not there says which path, and not the user's directory: it was
- // never looked for there, so naming it sends the user to read a file that is
- // present and say nothing about why it was ignored.
- func TestLoadProfileAMissingPathNamesThePath(t *testing.T) {
- userDir := t.TempDir()
- t.Cleanup(stubUserConfigDir(t, userDir))
- t.Cleanup(func() { profilesDirOverride = "" })
- t.Chdir(t.TempDir())
- _, err := LoadProfile("./nope.json")
- if err == nil {
- t.Fatal("LoadProfile(./nope.json) = nil error, want one")
- }
- if !strings.Contains(err.Error(), "./nope.json") {
- t.Errorf("LoadProfile(./nope.json) = %q, want it to name the path", err)
- }
- if strings.Contains(err.Error(), "qmk-rgb-tool") {
- t.Errorf("LoadProfile(./nope.json) = %q, want it not to name the user's directory", err)
- }
- }
- // A .json argument is read as a path and never sanitized into a name. This is the
- // case the rule turns on: `lala.json` used to mean the profile `lala-json`, and
- // the error for a missing one would name a file that never existed.
- func TestLoadProfileAJsonArgumentIsNotSanitized(t *testing.T) {
- userDir := t.TempDir()
- t.Cleanup(stubUserConfigDir(t, userDir))
- t.Cleanup(func() { profilesDirOverride = "" })
- t.Chdir(t.TempDir())
- _, err := LoadProfile("lala.json")
- if err == nil {
- t.Fatal("LoadProfile(lala.json) = nil error, want one")
- }
- if strings.Contains(err.Error(), "lala-json") {
- t.Errorf("LoadProfile(lala.json) = %q, want lala.json read as a path", err)
- }
- if !strings.Contains(err.Error(), "lala.json") {
- t.Errorf("LoadProfile(lala.json) = %q, want it to name the path it tried", err)
- }
- }
- // A save that names a file writes that file, and the profile's own name is the
- // file's name without the suffix — so the file and the name in it agree, and the
- // file is not silently also written where the names live.
- func TestSaveProfileToWritesThePathItWasGiven(t *testing.T) {
- userDir := t.TempDir()
- t.Cleanup(stubUserConfigDir(t, userDir))
- t.Cleanup(func() { profilesDirOverride = "" })
- here := t.TempDir()
- t.Chdir(here)
- path, name, isFile := resolveProfileTarget("profiles/new.json")
- if !isFile {
- t.Fatal(`resolveProfileTarget("profiles/new.json") isPath = false, want true`)
- }
- p := &Profile{Name: name, Version: 1}
- if err := p.saveTo(path); err != nil {
- t.Fatalf("saveTo(%q): %v", path, err)
- }
- written := filepath.Join(here, "profiles", "new.json")
- if _, err := os.Stat(written); err != nil {
- t.Fatalf("saveTo(%q) did not write the file it was given: %v", path, err)
- }
- data, err := os.ReadFile(written)
- if err != nil {
- t.Fatal(err)
- }
- var got Profile
- if err := json.Unmarshal(data, &got); err != nil {
- t.Fatal(err)
- }
- if got.Name != "new" {
- t.Errorf("saved profile Name = %q, want %q", got.Name, "new")
- }
- if _, err := os.Stat(filepath.Join(userDir, "qmk-rgb-tool", "profiles", "new.json")); err == nil {
- t.Error("saveTo also wrote into the user's directory, which the argument did not ask for")
- }
- }
- // A delete says which file it removed, and saying so must not make it resolve a
- // path: `delete` is name-only, so `delete lava.json` names no file, it names the
- // profile `lava-json`. A same-named file in the working directory is the thing
- // that would go if the report were read as permission to touch it.
- func TestDeleteIsNameOnlyAndSaysWhichFileItRemoved(t *testing.T) {
- userDir := t.TempDir()
- t.Cleanup(stubUserConfigDir(t, userDir))
- t.Cleanup(func() { profilesDirOverride = "" })
- here := t.TempDir()
- t.Chdir(here)
- inWorkingDir := filepath.Join(here, "lava.json")
- if err := os.WriteFile(inWorkingDir, []byte(`{"name":"lava","version":1,"zones":{}}`), 0o644); err != nil {
- t.Fatal(err)
- }
- dir := filepath.Join(userDir, "qmk-rgb-tool", "profiles")
- if err := os.MkdirAll(dir, 0o755); err != nil {
- t.Fatal(err)
- }
- saved := filepath.Join(dir, "lava.json")
- if err := os.WriteFile(saved, []byte(`{"name":"lava","version":1,"zones":{}}`), 0o644); err != nil {
- t.Fatal(err)
- }
- path, err := DeleteProfile("lava")
- if err != nil {
- t.Fatalf("DeleteProfile(lava): %v", err)
- }
- if path != saved {
- t.Errorf("DeleteProfile(lava) = %q, want %q", path, saved)
- }
- line := deletedProfileLine("lava", path)
- if !strings.Contains(line, saved) {
- t.Errorf("deletedProfileLine() = %q, want the file it removed", line)
- }
- if !strings.Contains(line, "your user directory") {
- t.Errorf("deletedProfileLine() = %q, want a name's file marked as the user's", line)
- }
- if _, err := os.Stat(saved); err == nil {
- t.Error("DeleteProfile(lava) left the profile in place")
- }
- if _, err := os.Stat(inWorkingDir); err != nil {
- t.Errorf("DeleteProfile(lava) removed the working directory's file: %v", err)
- }
- // The .json suffix is not read as a path by a name-only command: it names the
- // profile `lava-json`, which is not there.
- if _, err := DeleteProfile("lava.json"); err == nil {
- t.Error(`DeleteProfile("lava.json") = nil error, want the name form refused`)
- }
- if _, err := os.Stat(inWorkingDir); err != nil {
- t.Errorf(`DeleteProfile("lava.json") removed the working directory's file: %v`, err)
- }
- }
- // A save says which file it wrote, and the path is annotated the way every other
- // message in the tool is: a bare path does not say whether it is the user's
- // directory or one the argument named.
- func TestSavedProfileLineNamesTheFileItWrote(t *testing.T) {
- userDir := t.TempDir()
- t.Cleanup(stubUserConfigDir(t, userDir))
- t.Cleanup(func() { profilesDirOverride = "" })
- here := t.TempDir()
- t.Chdir(here)
- path, name, _ := resolveProfileTarget("profiles/new.json")
- got := savedProfileLine(name, path)
- if !strings.Contains(got, "profiles/new.json") {
- t.Errorf("savedProfileLine() = %q, want the path it was given", got)
- }
- if strings.Contains(got, "your user directory") {
- t.Errorf("savedProfileLine() = %q, want a file the argument named left unannotated", got)
- }
- byName, byNameStr, _ := resolveProfileTarget("lava")
- if line := savedProfileLine(byNameStr, byName); !strings.Contains(line, "your user directory") {
- t.Errorf("savedProfileLine() = %q, want a name's file marked as the user's", line)
- }
- }
- // A save by name goes to the user directory, which is the point of the name form:
- // the argument is a name, so it is the directory that decides where it goes.
- func TestSaveByNameGoesToTheUserDirectory(t *testing.T) {
- userDir := t.TempDir()
- t.Cleanup(stubUserConfigDir(t, userDir))
- t.Cleanup(func() { profilesDirOverride = "" })
- here := t.TempDir()
- t.Chdir(here)
- path, name, isFile := resolveProfileTarget("lava")
- if isFile {
- t.Fatal(`resolveProfileTarget("lava") isPath = true, want false`)
- }
- if err := (&Profile{Name: name, Version: 1}).saveTo(path); err != nil {
- t.Fatalf("saveTo(%q): %v", path, err)
- }
- if _, err := os.Stat(filepath.Join(userDir, "qmk-rgb-tool", "profiles", "lava.json")); err != nil {
- t.Fatalf("saveTo(%q) did not write into the user's directory: %v", path, err)
- }
- if _, err := os.Stat(filepath.Join(here, "lava.json")); err == nil {
- t.Error("saveTo wrote into the working directory, which a name does not ask for")
- }
- }
|