From ba857e6004b45eda2b856e5fe3a27e962cbef0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joshua=20T=C3=B6pfer?= Date: Sun, 24 Mar 2024 17:13:05 +0100 Subject: [PATCH] 344 fixed timer bug --- mob.go | 229 ++++---------------------------------------------- mob_test.go | 31 ------- timer.go | 228 +++++++++++++++++++++++++++++++++++++++++++++++++ timer_test.go | 94 +++++++++++++++++++++ 4 files changed, 337 insertions(+), 245 deletions(-) create mode 100644 timer.go create mode 100644 timer_test.go diff --git a/mob.go b/mob.go index f2a67faf..59529ea4 100644 --- a/mob.go +++ b/mob.go @@ -2,19 +2,11 @@ package main import ( "bufio" - "bytes" - "crypto/tls" - x509 "crypto/x509" - "encoding/json" "errors" "fmt" config "github.com/remotemobprogramming/mob/v4/configuration" "github.com/remotemobprogramming/mob/v4/help" - "github.com/remotemobprogramming/mob/v4/open" "github.com/remotemobprogramming/mob/v4/say" - "io" - "net/http" - "net/url" "os" "os/exec" "path/filepath" @@ -310,10 +302,13 @@ func execute(command string, parameter []string, configuration config.Configurat return } if len(parameter) > 0 { - timer := parameter[0] - startTimer(timer, configuration) + if err := startTimer(parameter[0], configuration); err != nil { + exit(1) + } } else if configuration.Timer != "" { - startTimer(configuration.Timer, configuration) + if err := startTimer(configuration.Timer, configuration); err != nil { + exit(1) + } } else { say.Info("It's now " + currentTime() + ". Happy collaborating! :)") } @@ -340,17 +335,22 @@ func execute(command string, parameter []string, configuration config.Configurat say.Error(fmt.Sprintf("Could not open webtimer: %s", err.Error())) } } else { - timer := parameter[0] - startTimer(timer, configuration) + if err := startTimer(parameter[0], configuration); err != nil { + exit(1) + } } } else if configuration.Timer != "" { - startTimer(configuration.Timer, configuration) + if err := startTimer(configuration.Timer, configuration); err != nil { + exit(1) + } } else { help.Help(configuration) } case "break": if len(parameter) > 0 { - startBreakTimer(parameter[0], configuration) + if err := startBreakTimer(parameter[0], configuration); err != nil { + exit(1) + } } else { help.Help(configuration) } @@ -371,22 +371,6 @@ func execute(command string, parameter []string, configuration config.Configurat } } -func openTimerInBrowser(configuration config.Configuration) error { - timerurl := configuration.TimerUrl - if timerurl == "" { - return fmt.Errorf("Timer url is not configured") - } - if configuration.TimerRoom != "" { - if !strings.HasSuffix(configuration.TimerUrl, "/") { - timerurl += "/" - } - timerurl += configuration.TimerRoom - } else { - say.Warning("Timer Room is not configured. To open specific room please configure timer room variable.") - } - return open.OpenInBrowser(timerurl) -} - func helpRequested(parameter []string) bool { for i := 0; i < len(parameter); i++ { element := parameter[i] @@ -507,189 +491,6 @@ func executeCommandsInBackgroundProcess(commands ...string) (err error) { return err } -func startTimer(timerInMinutes string, configuration config.Configuration) { - timeoutInMinutes := toMinutes(timerInMinutes) - - timeoutInSeconds := timeoutInMinutes * 60 - timeOfTimeout := time.Now().Add(time.Minute * time.Duration(timeoutInMinutes)).Format("15:04") - say.Debug(fmt.Sprintf("Starting timer at %s for %d minutes = %d seconds (parsed from user input %s)", timeOfTimeout, timeoutInMinutes, timeoutInSeconds, timerInMinutes)) - - room := getMobTimerRoom(configuration) - startRemoteTimer := room != "" - startLocalTimer := configuration.TimerLocal - - if !startRemoteTimer && !startLocalTimer { - say.Error("No timer configured, not starting timer") - exit(1) - } - - if startRemoteTimer { - timerUser := getUserForMobTimer(configuration.TimerUser) - err := httpPutTimer(timeoutInMinutes, room, timerUser, configuration.TimerUrl, configuration.TimerInsecure) - if err != nil { - say.Error("remote timer couldn't be started") - say.Error(err.Error()) - exit(1) - } - } - - if startLocalTimer { - err := executeCommandsInBackgroundProcess(getSleepCommand(timeoutInSeconds), getVoiceCommand(configuration.VoiceMessage, configuration.VoiceCommand), getNotifyCommand(configuration.NotifyMessage, configuration.NotifyCommand), "echo \"mobTimer\"") - - if err != nil { - say.Error(fmt.Sprintf("timer couldn't be started on your system (%s)", runtime.GOOS)) - say.Error(err.Error()) - exit(1) - } - } - - say.Info("It's now " + currentTime() + ". " + fmt.Sprintf("%d min timer ends at approx. %s", timeoutInMinutes, timeOfTimeout) + ". Happy collaborating! :)") -} - -func getMobTimerRoom(configuration config.Configuration) string { - if !isGit() { - say.Debug("timer not in git repository, using MOB_TIMER_ROOM for room name") - return configuration.TimerRoom - } - - currentWipBranchQualifier := configuration.WipBranchQualifier - if currentWipBranchQualifier == "" { - currentBranch := gitCurrentBranch() - currentBaseBranch, _ := determineBranches(currentBranch, gitBranches(), configuration) - - if currentBranch.IsWipBranch(configuration) { - wipBranchWithoutWipPrefix := currentBranch.removeWipPrefix(configuration).Name - currentWipBranchQualifier = removePrefix(removePrefix(wipBranchWithoutWipPrefix, currentBaseBranch.Name), configuration.WipBranchQualifierSeparator) - } - } - - if configuration.TimerRoomUseWipBranchQualifier && currentWipBranchQualifier != "" { - say.Info("Using wip branch qualifier for room name") - return currentWipBranchQualifier - } - - return configuration.TimerRoom -} - -func startBreakTimer(timerInMinutes string, configuration config.Configuration) { - timeoutInMinutes := toMinutes(timerInMinutes) - - timeoutInSeconds := timeoutInMinutes * 60 - timeOfTimeout := time.Now().Add(time.Minute * time.Duration(timeoutInMinutes)).Format("15:04") - say.Debug(fmt.Sprintf("Starting break timer at %s for %d minutes = %d seconds (parsed from user input %s)", timeOfTimeout, timeoutInMinutes, timeoutInSeconds, timerInMinutes)) - - room := getMobTimerRoom(configuration) - startRemoteTimer := room != "" - startLocalTimer := configuration.TimerLocal - - if !startRemoteTimer && !startLocalTimer { - say.Error("No break timer configured, not starting break timer") - exit(1) - } - - if startRemoteTimer { - timerUser := getUserForMobTimer(configuration.TimerUser) - err := httpPutBreakTimer(timeoutInMinutes, room, timerUser, configuration.TimerUrl, configuration.TimerInsecure) - - if err != nil { - say.Error("remote break timer couldn't be started") - say.Error(err.Error()) - exit(1) - } - } - - if startLocalTimer { - err := executeCommandsInBackgroundProcess(getSleepCommand(timeoutInSeconds), getVoiceCommand("mob start", configuration.VoiceCommand), getNotifyCommand("mob start", configuration.NotifyCommand), "echo \"mobTimer\"") - - if err != nil { - say.Error(fmt.Sprintf("break timer couldn't be started on your system (%s)", runtime.GOOS)) - say.Error(err.Error()) - exit(1) - } - } - - say.Info("It's now " + currentTime() + ". " + fmt.Sprintf("%d min break timer ends at approx. %s", timeoutInMinutes, timeOfTimeout) + ". Happy collaborating! :)") -} - -func getUserForMobTimer(userOverride string) string { - if userOverride == "" { - return gitUserName() - } - return userOverride -} - -func toMinutes(timerInMinutes string) int { - timeoutInMinutes, _ := strconv.Atoi(timerInMinutes) - if timeoutInMinutes < 0 { - timeoutInMinutes = 0 - } - return timeoutInMinutes -} - -func httpPutTimer(timeoutInMinutes int, room string, user string, timerService string, disableSSLVerification bool) error { - putBody, _ := json.Marshal(map[string]interface{}{ - "timer": timeoutInMinutes, - "user": user, - }) - return sendRequest(putBody, "PUT", timerService+room, disableSSLVerification) -} - -func httpPutBreakTimer(timeoutInMinutes int, room string, user string, timerService string, disableSSLVerification bool) error { - putBody, _ := json.Marshal(map[string]interface{}{ - "breaktimer": timeoutInMinutes, - "user": user, - }) - return sendRequest(putBody, "PUT", timerService+room, disableSSLVerification) -} - -func sendRequest(requestBody []byte, requestMethod string, requestUrl string, disableSSLVerification bool) error { - say.Info(requestMethod + " " + requestUrl + " " + string(requestBody)) - - responseBody := bytes.NewBuffer(requestBody) - request, requestCreationError := http.NewRequest(requestMethod, requestUrl, responseBody) - - httpClient := http.DefaultClient - if disableSSLVerification { - transCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - httpClient = &http.Client{Transport: transCfg} - } - - if requestCreationError != nil { - return fmt.Errorf("failed to create the http request object: %w", requestCreationError) - } - - request.Header.Set("Content-Type", "application/json") - response, responseErr := httpClient.Do(request) - if e, ok := responseErr.(*url.Error); ok { - switch e.Err.(type) { - case x509.UnknownAuthorityError: - say.Error("The timer.mob.sh SSL certificate is signed by an unknown authority!") - say.Fix("HINT: You can ignore that by adding MOB_TIMER_INSECURE=true to your configuration or environment.", - "echo MOB_TIMER_INSECURE=true >> ~/.mob") - return fmt.Errorf("failed, to amke the http request: %w", responseErr) - - default: - return fmt.Errorf("failed to make the http request: %w", responseErr) - - } - } - - if responseErr != nil { - return fmt.Errorf("failed to make the http request: %w", responseErr) - } - defer response.Body.Close() - body, responseReadingErr := io.ReadAll(response.Body) - if responseReadingErr != nil { - return fmt.Errorf("failed to read the http response: %w", responseReadingErr) - } - if string(body) != "" { - say.Info(string(body)) - } - return nil -} - func currentTime() string { return time.Now().Format("15:04") } diff --git a/mob_test.go b/mob_test.go index 7fb0d716..4f065511 100644 --- a/mob_test.go +++ b/mob_test.go @@ -1723,37 +1723,6 @@ func TestHelpRequested(t *testing.T) { equals(t, true, helpRequested([]string{"s", "10", "-h"})) } -func TestOpenTimerInBrowserWithTimerRoom(t *testing.T) { - mockOpenInBrowser() - output, configuration := setup(t) - configuration.TimerRoom = "testroom" - - err := openTimerInBrowser(configuration) - - assertOutputNotContains(t, output, "Timer Room is not configured.") - assertNoError(t, err) -} - -func TestOpenTimerInBrowserWithoutTimerRoom(t *testing.T) { - mockOpenInBrowser() - output, configuration := setup(t) - - err := openTimerInBrowser(configuration) - - assertOutputContains(t, output, "Timer Room is not configured.") - assertNoError(t, err) -} - -func TestOpenTimerInBrowserError(t *testing.T) { - mockOpenInBrowser() - _, configuration := setup(t) - configuration.TimerUrl = "" - - err := openTimerInBrowser(configuration) - - assertError(t, err, "Timer url is not configured") -} - func TestMobClean(t *testing.T) { _, configuration := setup(t) diff --git a/timer.go b/timer.go new file mode 100644 index 00000000..d875fc05 --- /dev/null +++ b/timer.go @@ -0,0 +1,228 @@ +package main + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + config "github.com/remotemobprogramming/mob/v4/configuration" + "github.com/remotemobprogramming/mob/v4/open" + "github.com/remotemobprogramming/mob/v4/say" + "io" + "net/http" + "net/url" + "runtime" + "strconv" + "strings" + "time" +) + +func startTimer(timerInMinutes string, configuration config.Configuration) error { + err, timeoutInMinutes := toMinutes(timerInMinutes) + if err != nil { + return err + } + + timeoutInSeconds := timeoutInMinutes * 60 + timeOfTimeout := time.Now().Add(time.Minute * time.Duration(timeoutInMinutes)).Format("15:04") + say.Debug(fmt.Sprintf("Starting timer at %s for %d minutes = %d seconds (parsed from user input %s)", timeOfTimeout, timeoutInMinutes, timeoutInSeconds, timerInMinutes)) + + room := getMobTimerRoom(configuration) + startRemoteTimer := room != "" + startLocalTimer := configuration.TimerLocal + + if !startRemoteTimer && !startLocalTimer { + say.Error("No timer configured, not starting timer") + exit(1) + } + + if startRemoteTimer { + timerUser := getUserForMobTimer(configuration.TimerUser) + err := httpPutTimer(timeoutInMinutes, room, timerUser, configuration.TimerUrl, configuration.TimerInsecure) + if err != nil { + say.Error("remote timer couldn't be started") + say.Error(err.Error()) + exit(1) + } + } + + if startLocalTimer { + err := executeCommandsInBackgroundProcess(getSleepCommand(timeoutInSeconds), getVoiceCommand(configuration.VoiceMessage, configuration.VoiceCommand), getNotifyCommand(configuration.NotifyMessage, configuration.NotifyCommand), "echo \"mobTimer\"") + + if err != nil { + say.Error(fmt.Sprintf("timer couldn't be started on your system (%s)", runtime.GOOS)) + say.Error(err.Error()) + exit(1) + } + } + + say.Info("It's now " + currentTime() + ". " + fmt.Sprintf("%d min timer ends at approx. %s", timeoutInMinutes, timeOfTimeout) + ". Happy collaborating! :)") + return nil +} + +func getMobTimerRoom(configuration config.Configuration) string { + if !isGit() { + say.Debug("timer not in git repository, using MOB_TIMER_ROOM for room name") + return configuration.TimerRoom + } + + currentWipBranchQualifier := configuration.WipBranchQualifier + if currentWipBranchQualifier == "" { + currentBranch := gitCurrentBranch() + currentBaseBranch, _ := determineBranches(currentBranch, gitBranches(), configuration) + + if currentBranch.IsWipBranch(configuration) { + wipBranchWithoutWipPrefix := currentBranch.removeWipPrefix(configuration).Name + currentWipBranchQualifier = removePrefix(removePrefix(wipBranchWithoutWipPrefix, currentBaseBranch.Name), configuration.WipBranchQualifierSeparator) + } + } + + if configuration.TimerRoomUseWipBranchQualifier && currentWipBranchQualifier != "" { + say.Info("Using wip branch qualifier for room name") + return currentWipBranchQualifier + } + + return configuration.TimerRoom +} + +func startBreakTimer(timerInMinutes string, configuration config.Configuration) error { + err, timeoutInMinutes := toMinutes(timerInMinutes) + if err != nil { + return err + } + + timeoutInSeconds := timeoutInMinutes * 60 + timeOfTimeout := time.Now().Add(time.Minute * time.Duration(timeoutInMinutes)).Format("15:04") + say.Debug(fmt.Sprintf("Starting break timer at %s for %d minutes = %d seconds (parsed from user input %s)", timeOfTimeout, timeoutInMinutes, timeoutInSeconds, timerInMinutes)) + + room := getMobTimerRoom(configuration) + startRemoteTimer := room != "" + startLocalTimer := configuration.TimerLocal + + if !startRemoteTimer && !startLocalTimer { + say.Error("No break timer configured, not starting break timer") + exit(1) + } + + if startRemoteTimer { + timerUser := getUserForMobTimer(configuration.TimerUser) + err := httpPutBreakTimer(timeoutInMinutes, room, timerUser, configuration.TimerUrl, configuration.TimerInsecure) + + if err != nil { + say.Error("remote break timer couldn't be started") + say.Error(err.Error()) + exit(1) + } + } + + if startLocalTimer { + err := executeCommandsInBackgroundProcess(getSleepCommand(timeoutInSeconds), getVoiceCommand("mob start", configuration.VoiceCommand), getNotifyCommand("mob start", configuration.NotifyCommand), "echo \"mobTimer\"") + + if err != nil { + say.Error(fmt.Sprintf("break timer couldn't be started on your system (%s)", runtime.GOOS)) + say.Error(err.Error()) + exit(1) + } + } + + say.Info("It's now " + currentTime() + ". " + fmt.Sprintf("%d min break timer ends at approx. %s", timeoutInMinutes, timeOfTimeout) + ". So take a break now! :)") + return nil +} + +func getUserForMobTimer(userOverride string) string { + if userOverride == "" { + return gitUserName() + } + return userOverride +} + +func toMinutes(timerInMinutes string) (error, int) { + timeoutInMinutes, err := strconv.Atoi(timerInMinutes) + if err != nil || timeoutInMinutes < 1 { + say.Error(fmt.Sprintf("The parameter must be an integer number greater then zero")) + return errors.New("The parameter must be an integer number greater then zero"), 0 + } + return nil, timeoutInMinutes +} + +func httpPutTimer(timeoutInMinutes int, room string, user string, timerService string, disableSSLVerification bool) error { + putBody, _ := json.Marshal(map[string]interface{}{ + "timer": timeoutInMinutes, + "user": user, + }) + return sendRequest(putBody, "PUT", timerService+room, disableSSLVerification) +} + +func httpPutBreakTimer(timeoutInMinutes int, room string, user string, timerService string, disableSSLVerification bool) error { + putBody, _ := json.Marshal(map[string]interface{}{ + "breaktimer": timeoutInMinutes, + "user": user, + }) + return sendRequest(putBody, "PUT", timerService+room, disableSSLVerification) +} + +func sendRequest(requestBody []byte, requestMethod string, requestUrl string, disableSSLVerification bool) error { + say.Info(requestMethod + " " + requestUrl + " " + string(requestBody)) + + responseBody := bytes.NewBuffer(requestBody) + request, requestCreationError := http.NewRequest(requestMethod, requestUrl, responseBody) + + httpClient := http.DefaultClient + if disableSSLVerification { + transCfg := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + httpClient = &http.Client{Transport: transCfg} + } + + if requestCreationError != nil { + return fmt.Errorf("failed to create the http request object: %w", requestCreationError) + } + + request.Header.Set("Content-Type", "application/json") + response, responseErr := httpClient.Do(request) + if e, ok := responseErr.(*url.Error); ok { + switch e.Err.(type) { + case x509.UnknownAuthorityError: + say.Error("The timer.mob.sh SSL certificate is signed by an unknown authority!") + say.Fix("HINT: You can ignore that by adding MOB_TIMER_INSECURE=true to your configuration or environment.", + "echo MOB_TIMER_INSECURE=true >> ~/.mob") + return fmt.Errorf("failed, to amke the http request: %w", responseErr) + + default: + return fmt.Errorf("failed to make the http request: %w", responseErr) + + } + } + + if responseErr != nil { + return fmt.Errorf("failed to make the http request: %w", responseErr) + } + defer response.Body.Close() + body, responseReadingErr := io.ReadAll(response.Body) + if responseReadingErr != nil { + return fmt.Errorf("failed to read the http response: %w", responseReadingErr) + } + if string(body) != "" { + say.Info(string(body)) + } + return nil +} + +func openTimerInBrowser(configuration config.Configuration) error { + timerurl := configuration.TimerUrl + if timerurl == "" { + return fmt.Errorf("Timer url is not configured") + } + if configuration.TimerRoom != "" { + if !strings.HasSuffix(configuration.TimerUrl, "/") { + timerurl += "/" + } + timerurl += configuration.TimerRoom + } else { + say.Warning("Timer Room is not configured. To open specific room please configure timer room variable.") + } + return open.OpenInBrowser(timerurl) +} diff --git a/timer_test.go b/timer_test.go new file mode 100644 index 00000000..3fa24d10 --- /dev/null +++ b/timer_test.go @@ -0,0 +1,94 @@ +package main + +import "testing" + +func TestOpenTimerInBrowserWithTimerRoom(t *testing.T) { + mockOpenInBrowser() + output, configuration := setup(t) + configuration.TimerRoom = "testroom" + + err := openTimerInBrowser(configuration) + + assertOutputNotContains(t, output, "Timer Room is not configured.") + assertNoError(t, err) +} + +func TestOpenTimerInBrowserWithoutTimerRoom(t *testing.T) { + mockOpenInBrowser() + output, configuration := setup(t) + + err := openTimerInBrowser(configuration) + + assertOutputContains(t, output, "Timer Room is not configured.") + assertNoError(t, err) +} + +func TestOpenTimerInBrowserError(t *testing.T) { + mockOpenInBrowser() + _, configuration := setup(t) + configuration.TimerUrl = "" + + err := openTimerInBrowser(configuration) + + assertError(t, err, "Timer url is not configured") +} + +func TestTimerNumberLessThen1(t *testing.T) { + output, configuration := setup(t) + + err := startTimer("0", configuration) + + assertError(t, err, "The parameter must be an integer number greater then zero") + assertOutputContains(t, output, "The parameter must be an integer number greater then zero") +} + +func TestTimerNotANumber(t *testing.T) { + output, configuration := setup(t) + + err := startTimer("NotANumber", configuration) + + assertError(t, err, "The parameter must be an integer number greater then zero") + assertOutputContains(t, output, "The parameter must be an integer number greater then zero") +} + +func TestTimer(t *testing.T) { + output, configuration := setup(t) + configuration.NotifyCommand = "" + configuration.VoiceCommand = "" + + err := startTimer("1", configuration) + + assertNoError(t, err) + assertOutputContains(t, output, "1 min timer ends at approx.") + assertOutputContains(t, output, "Happy collaborating! :)") +} + +func TestBreakTimerNumberLessThen1(t *testing.T) { + output, configuration := setup(t) + + err := startBreakTimer("0", configuration) + + assertError(t, err, "The parameter must be an integer number greater then zero") + assertOutputContains(t, output, "The parameter must be an integer number greater then zero") +} + +func TestBreakTimerNotANumber(t *testing.T) { + output, configuration := setup(t) + + err := startBreakTimer("NotANumber", configuration) + + assertError(t, err, "The parameter must be an integer number greater then zero") + assertOutputContains(t, output, "The parameter must be an integer number greater then zero") +} + +func TestBreakTimer(t *testing.T) { + output, configuration := setup(t) + configuration.NotifyCommand = "" + configuration.VoiceCommand = "" + + err := startBreakTimer("1", configuration) + + assertNoError(t, err) + assertOutputContains(t, output, "1 min break timer ends at approx.") + assertOutputContains(t, output, "So take a break now! :)") +}