From 3d450a1175105547232045b8a22d32f04dd2ddb9 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sun, 9 Jan 2022 11:00:53 -0800 Subject: [PATCH] in progress integration tests --- clients/local/client.go | 33 ++++++----- clients/local/client_test.go | 24 ++++++++ clients/local/test_interaction.sh | 8 +++ clients/remote/client.go | 40 ++++++------- go.mod | 17 ++++-- go.sum | 5 ++ server/server.go | 47 +-------------- server/server_test.go | 71 +++++++++++++++++++++- shared/client.go | 97 +++++++++++++++++++++++++------ shared/client_test.go | 6 +- shared/data.go | 40 ++++++++++++- shared/testutils.go | 4 +- 12 files changed, 283 insertions(+), 109 deletions(-) create mode 100644 clients/local/client_test.go create mode 100644 clients/local/test_interaction.sh diff --git a/clients/local/client.go b/clients/local/client.go index b552de5..0f22749 100644 --- a/clients/local/client.go +++ b/clients/local/client.go @@ -2,6 +2,7 @@ package main import ( "os" + "strings" "github.com/ddworken/hishtory/shared" ) @@ -13,10 +14,11 @@ func main() { case "query": query() case "init": - err := shared.Setup(os.Args) - if err != nil { - panic(err) - } + shared.CheckFatalError(shared.Setup(os.Args)) + case "enable": + shared.CheckFatalError(shared.Enable()) + case "disable": + shared.CheckFatalError(shared.Disable()) } } @@ -28,19 +30,24 @@ func getServerHostname() string { } func query() { - // TODO(ddworken) - var data []*shared.HistoryEntry + userSecret, err := shared.GetUserSecret() + shared.CheckFatalError(err) + db, err := shared.OpenDB() + shared.CheckFatalError(err) + query := strings.Join(os.Args[2:], " ") + data, err := shared.Search(db, query, userSecret, 25) + shared.CheckFatalError(err) shared.DisplayResults(data) } func saveHistoryEntry() { + isEnabled, err := shared.IsEnabled() + shared.CheckFatalError(err) + if !isEnabled { + return + } entry, err := shared.BuildHistoryEntry(os.Args) - if err != nil { - panic(err) - } - + shared.CheckFatalError(err) err = shared.Persist(*entry) - if err != nil { - panic(err) - } + shared.CheckFatalError(err) } diff --git a/clients/local/client_test.go b/clients/local/client_test.go new file mode 100644 index 0000000..28be339 --- /dev/null +++ b/clients/local/client_test.go @@ -0,0 +1,24 @@ +package main + +import ( + "bytes" + "fmt" + "os/exec" + "testing" + + "github.com/ddworken/hishtory/shared" +) + +func TestIntegration(t *testing.T) { + // Set up + defer shared.BackupAndRestore(t) + + // Run the test + cmd := exec.Command("bash", "--init-file", "test_interaction.sh") + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + t.Fatalf("unexpected error when running test script: %v", err) + } + fmt.Printf("%q\n", out.String()) +} diff --git a/clients/local/test_interaction.sh b/clients/local/test_interaction.sh new file mode 100644 index 0000000..65b9995 --- /dev/null +++ b/clients/local/test_interaction.sh @@ -0,0 +1,8 @@ +go build -o /tmp/client clients/local/client.go +/tmp/client init +export PROMPT_COMMAND='/tmp/client upload $? "`history 1`"' +ls /a +ls /bar +ls /foo +echo foo +/tmp/client query \ No newline at end of file diff --git a/clients/remote/client.go b/clients/remote/client.go index 6f17498..1d5b49b 100644 --- a/clients/remote/client.go +++ b/clients/remote/client.go @@ -23,6 +23,10 @@ func main() { if err != nil { panic(err) } + case "enable": + shared.CheckFatalError(shared.Enable()) + case "disable": + shared.CheckFatalError(shared.Disable()) } } @@ -35,14 +39,10 @@ func getServerHostname() string { func query() { userSecret, err := shared.GetUserSecret() - if err != nil { - panic(err) - } + shared.CheckFatalError(err) req, err := http.NewRequest("GET", getServerHostname()+"/api/v1/search", nil) - if err != nil { - panic(err) - } + shared.CheckFatalError(err) q := req.URL.Query() q.Add("query", strings.Join(os.Args[2:], " ")) @@ -52,36 +52,30 @@ func query() { client := &http.Client{} resp, err := client.Do(req) - if err != nil { - panic(err) - } + shared.CheckFatalError(err) defer resp.Body.Close() resp_body, err := ioutil.ReadAll(resp.Body) - if err != nil { - panic(err) - } + shared.CheckFatalError(err) if resp.Status != "200 OK" { - panic("search API returned invalid result. status=" + resp.Status) + shared.CheckFatalError(fmt.Errorf("search API returned invalid result. status=" + resp.Status)) } var data []*shared.HistoryEntry err = json.Unmarshal(resp_body, &data) - if err != nil { - panic(err) - } + shared.CheckFatalError(err) shared.DisplayResults(data) } func saveHistoryEntry() { + isEnabled, err := shared.IsEnabled() + shared.CheckFatalError(err) + if !isEnabled { + return + } entry, err := shared.BuildHistoryEntry(os.Args) - if err != nil { - panic(err) - } - + shared.CheckFatalError(err) err = send(*entry) - if err != nil { - panic(err) - } + shared.CheckFatalError(err) } func send(entry shared.HistoryEntry) error { diff --git a/go.mod b/go.mod index ad76c31..4b1aff8 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,20 @@ module github.com/ddworken/hishtory -go 1.13 +go 1.17 require ( - github.com/fatih/color v1.13.0 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/rodaine/table v1.0.1 // indirect + github.com/fatih/color v1.13.0 + github.com/google/uuid v1.3.0 + github.com/rodaine/table v1.0.1 gorm.io/driver/sqlite v1.2.6 gorm.io/gorm v1.22.4 ) + +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.3 // indirect + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-sqlite3 v1.14.9 // indirect + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect +) diff --git a/go.sum b/go.sum index 646d6c5..6a8a925 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -14,20 +15,24 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ= github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4= gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY= diff --git a/server/server.go b/server/server.go index 7065c72..b7de13a 100644 --- a/server/server.go +++ b/server/server.go @@ -6,51 +6,10 @@ import ( "log" "net/http" "strconv" - "strings" - - "gorm.io/gorm" "github.com/ddworken/hishtory/shared" ) -func search(db *gorm.DB, userSecret, query string, limit int) ([]*shared.HistoryEntry, error) { - fmt.Println("Received search query: " + query) - tokens, err := tokenize(query) - if err != nil { - return nil, fmt.Errorf("failed to tokenize query: %v", err) - } - tx := db.Debug().Where("user_secret = ?", userSecret) - for _, token := range tokens { - if strings.Contains(token, ":") { - splitToken := strings.SplitN(token, ":", 2) - field := splitToken[0] - val := splitToken[1] - // tx = tx.Where() - panic("TODO(ddworken): Use " + field + val) - } else { - wildcardedToken := "%" + token + "%" - tx = tx.Where("(command LIKE ? OR hostname LIKE ? OR current_working_directory LIKE ?)", wildcardedToken, wildcardedToken, wildcardedToken) - } - } - tx = tx.Order("end_time DESC") - if limit > 0 { - tx = tx.Limit(limit) - } - var historyEntries []*shared.HistoryEntry - result := tx.Find(&historyEntries) - if result.Error != nil { - return nil, fmt.Errorf("DB query error: %v", result.Error) - } - return historyEntries, nil -} - -func tokenize(query string) ([]string, error) { - if query == "" { - return []string{}, nil - } - return strings.Split(query, " "), nil -} - func apiSubmitHandler(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var entry shared.HistoryEntry @@ -66,10 +25,8 @@ func apiSubmitHandler(w http.ResponseWriter, r *http.Request) { func apiSearchHandler(w http.ResponseWriter, r *http.Request) { userSecret := r.URL.Query().Get("user_secret") - if userSecret == "" { - panic("cannot search without specifying a user secret") - } query := r.URL.Query().Get("query") + fmt.Println("Received search query: " + query) limitStr := r.URL.Query().Get("limit") limit, err := strconv.Atoi(limitStr) if err != nil { @@ -79,7 +36,7 @@ func apiSearchHandler(w http.ResponseWriter, r *http.Request) { if err != nil { panic(err) } - entries, err := search(db, userSecret, query, limit) + entries, err := shared.Search(db, userSecret, query, limit) if err != nil { panic(err) } diff --git a/server/server_test.go b/server/server_test.go index 5cf462b..9502c2e 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -44,10 +44,10 @@ func TestSubmitThenQuery(t *testing.T) { shared.Check(t, err) var retrievedEntries []*shared.HistoryEntry shared.Check(t, json.Unmarshal(data, &retrievedEntries)) - dbEntry := retrievedEntries[0] if len(retrievedEntries) != 1 { t.Fatalf("Expected to retrieve 1 entry, found %d", len(retrievedEntries)) } + dbEntry := retrievedEntries[0] if dbEntry.UserSecret != "" { t.Fatalf("Response contains a user secret: %#v", *dbEntry) } @@ -56,3 +56,72 @@ func TestSubmitThenQuery(t *testing.T) { t.Fatalf("DB data is different than input! \ndb =%#v\ninput=%#v", *dbEntry, *entry) } } + +func TestNoUserSecretGivesNoResults(t *testing.T) { + // Set up + defer shared.BackupAndRestore(t) + shared.Check(t, shared.Setup([]string{})) + + // Submit an entry + entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / "}) + shared.Check(t, err) + reqBody, err := json.Marshal(entry) + shared.Check(t, err) + submitReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) + apiSubmitHandler(nil, submitReq) + + // Retrieve entries with no user secret + w := httptest.NewRecorder() + searchReq := httptest.NewRequest(http.MethodGet, "/", nil) + apiSearchHandler(w, searchReq) + res := w.Result() + defer res.Body.Close() + data, err := ioutil.ReadAll(res.Body) + shared.Check(t, err) + var retrievedEntries []*shared.HistoryEntry + shared.Check(t, json.Unmarshal(data, &retrievedEntries)) + if len(retrievedEntries) != 0 { + t.Fatalf("Expected to retrieve 0 entries, found %d", len(retrievedEntries)) + } +} + +func TestSearchQuery(t *testing.T) { + // Set up + defer shared.BackupAndRestore(t) + shared.Check(t, shared.Setup([]string{})) + + // Submit an entry that we'll match + entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /bar "}) + shared.Check(t, err) + reqBody, err := json.Marshal(entry) + shared.Check(t, err) + submitReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) + apiSubmitHandler(nil, submitReq) + // Submit an entry that we won't match + entry, err = shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /foo "}) + shared.Check(t, err) + reqBody, err = json.Marshal(entry) + shared.Check(t, err) + submitReq = httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) + apiSubmitHandler(nil, submitReq) + + // Retrieve the entry + secret, err := shared.GetUserSecret() + shared.Check(t, err) + w := httptest.NewRecorder() + searchReq := httptest.NewRequest(http.MethodGet, "/?user_secret="+secret+"&query=foo", nil) + apiSearchHandler(w, searchReq) + res := w.Result() + defer res.Body.Close() + data, err := ioutil.ReadAll(res.Body) + shared.Check(t, err) + var retrievedEntries []*shared.HistoryEntry + shared.Check(t, json.Unmarshal(data, &retrievedEntries)) + if len(retrievedEntries) != 1 { + t.Fatalf("Expected to retrieve 1 entry, found %d", len(retrievedEntries)) + } + dbEntry := retrievedEntries[0] + if dbEntry.Command != "ls /foo" { + t.Fatalf("Response contains an unexpected command: %#v", *dbEntry) + } +} diff --git a/shared/client.go b/shared/client.go index 58bf93e..ea309dc 100644 --- a/shared/client.go +++ b/shared/client.go @@ -1,7 +1,9 @@ package shared import ( + "encoding/json" "fmt" + "log" "os" "os/user" "path" @@ -15,7 +17,7 @@ import ( ) const ( - SECRET_PATH = ".hishtory.secret" + CONFIG_PATH = ".hishtory.config" ) func getCwd() (string, error) { @@ -91,15 +93,11 @@ func getLastCommand(history string) (string, error) { } func GetUserSecret() (string, error) { - homedir, err := os.UserHomeDir() + config, err := GetConfig() if err != nil { - return "", fmt.Errorf("failed to read secret hishtory key: %v", err) + return "", err } - secret, err := os.ReadFile(path.Join(homedir, SECRET_PATH)) - if err != nil { - return "", fmt.Errorf("failed to read secret hishtory key: %v", err) - } - return string(secret), nil + return config.UserSecret, nil } func Setup(args []string) error { @@ -109,15 +107,10 @@ func Setup(args []string) error { } fmt.Println("Setting secret hishtory key to " + string(userSecret)) - homedir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("failed to retrieve homedir: %v", err) - } - err = os.WriteFile(path.Join(homedir, SECRET_PATH), []byte(userSecret), 0600) - if err != nil { - return fmt.Errorf("failed to write hishtory secret: %v", err) - } - return nil + var config ClientConfig + config.UserSecret = userSecret + config.IsEnabled = true + return SetConfig(config) } func DisplayResults(results []*HistoryEntry) { @@ -131,3 +124,73 @@ func DisplayResults(results []*HistoryEntry) { tbl.Print() } + +type ClientConfig struct { + UserSecret string `json:"user_secret"` + IsEnabled bool `json:"is_enabled"` +} + +func GetConfig() (ClientConfig, error) { + homedir, err := os.UserHomeDir() + if err != nil { + return ClientConfig{}, fmt.Errorf("failed to retrieve homedir: %v", err) + } + data, err := os.ReadFile(path.Join(homedir, CONFIG_PATH)) + if err != nil { + return ClientConfig{}, fmt.Errorf("failed to read config file: %v", err) + } + var config ClientConfig + err = json.Unmarshal(data, &config) + if err != nil { + return ClientConfig{}, fmt.Errorf("failed to parse config file: %v", err) + } + return config, nil +} + +func SetConfig(config ClientConfig) error { + serializedConfig, err := json.Marshal(config) + if err != nil { + return fmt.Errorf("failed to serialize config: %v", err) + } + homedir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to retrieve homedir: %v", err) + } + err = os.WriteFile(path.Join(homedir, CONFIG_PATH), serializedConfig, 0600) + if err != nil { + return fmt.Errorf("failed to write config: %v", err) + } + return nil +} + +func IsEnabled() (bool, error) { + config, err := GetConfig() + if err != nil { + return false, err + } + return config.IsEnabled, nil +} + +func Enable() error { + config, err := GetConfig() + if err != nil { + return err + } + config.IsEnabled = true + return SetConfig(config) +} + +func Disable() error { + config, err := GetConfig() + if err != nil { + return err + } + config.IsEnabled = false + return SetConfig(config) +} + +func CheckFatalError(err error) { + if err != nil { + log.Fatalf("hishtory fatal error: %v", err) + } +} diff --git a/shared/client_test.go b/shared/client_test.go index 2019084..6fcbb81 100644 --- a/shared/client_test.go +++ b/shared/client_test.go @@ -11,14 +11,14 @@ func TestSetup(t *testing.T) { defer BackupAndRestore(t) homedir, err := os.UserHomeDir() Check(t, err) - if _, err := os.Stat(path.Join(homedir, SECRET_PATH)); err == nil { + if _, err := os.Stat(path.Join(homedir, CONFIG_PATH)); err == nil { t.Fatalf("hishtory secret file already exists!") } Check(t, Setup([]string{})) - if _, err := os.Stat(path.Join(homedir, SECRET_PATH)); err != nil { + if _, err := os.Stat(path.Join(homedir, CONFIG_PATH)); err != nil { t.Fatalf("hishtory secret file does not exist after Setup()!") } - data, err := os.ReadFile(path.Join(homedir, SECRET_PATH)) + data, err := os.ReadFile(path.Join(homedir, CONFIG_PATH)) Check(t, err) if len(data) < 10 { t.Fatalf("hishtory secret has unexpected length: %d", len(data)) diff --git a/shared/data.go b/shared/data.go index c6f7677..547ae7e 100644 --- a/shared/data.go +++ b/shared/data.go @@ -5,6 +5,7 @@ import ( "log" "os" "path" + "strings" "time" "gorm.io/driver/sqlite" @@ -12,7 +13,7 @@ import ( ) type HistoryEntry struct { - UserSecret string `json:"user_secret"` + UserSecret string `json:"user_secret" gorm:"index"` LocalUsername string `json:"local_username"` Hostname string `json:"hostname"` Command string `json:"command"` @@ -50,3 +51,40 @@ func OpenDB() (*gorm.DB, error) { db.AutoMigrate(&HistoryEntry{}) return db, nil } + +func Search(db *gorm.DB, userSecret, query string, limit int) ([]*HistoryEntry, error) { + tokens, err := tokenize(query) + if err != nil { + return nil, fmt.Errorf("failed to tokenize query: %v", err) + } + tx := db.Where("user_secret = ?", userSecret) + for _, token := range tokens { + if strings.Contains(token, ":") { + splitToken := strings.SplitN(token, ":", 2) + field := splitToken[0] + val := splitToken[1] + // tx = tx.Where() + panic("TODO(ddworken): Use " + field + val) + } else { + wildcardedToken := "%" + token + "%" + tx = tx.Where("(command LIKE ? OR hostname LIKE ? OR current_working_directory LIKE ?)", wildcardedToken, wildcardedToken, wildcardedToken) + } + } + tx = tx.Order("end_time DESC") + if limit > 0 { + tx = tx.Limit(limit) + } + var historyEntries []*HistoryEntry + result := tx.Find(&historyEntries) + if result.Error != nil { + return nil, fmt.Errorf("DB query error: %v", result.Error) + } + return historyEntries, nil +} + +func tokenize(query string) ([]string, error) { + if query == "" { + return []string{}, nil + } + return strings.Split(query, " "), nil +} diff --git a/shared/testutils.go b/shared/testutils.go index 8f59ebb..7520438 100644 --- a/shared/testutils.go +++ b/shared/testutils.go @@ -20,10 +20,10 @@ func BackupAndRestore(t *testing.T) func() { } os.Rename(path.Join(homedir, DB_PATH), path.Join(homedir, DB_PATH+".bak")) - os.Rename(path.Join(homedir, SECRET_PATH), path.Join(homedir, SECRET_PATH+".bak")) + os.Rename(path.Join(homedir, CONFIG_PATH), path.Join(homedir, CONFIG_PATH+".bak")) return func() { Check(t, os.Rename(path.Join(homedir, DB_PATH+".bak"), path.Join(homedir, DB_PATH))) - Check(t, os.Rename(path.Join(homedir, SECRET_PATH+".bak"), path.Join(homedir, SECRET_PATH))) + Check(t, os.Rename(path.Join(homedir, CONFIG_PATH+".bak"), path.Join(homedir, CONFIG_PATH))) } }