diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 92d7ae5..2063023 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,7 +26,7 @@ on:
- main
env:
- VERSION_NUMBER: 'v1.3.0'
+ VERSION_NUMBER: 'v1.3.1'
DOCKERHUB_REGISTRY_NAME: 'digitalghostdev/poke-cli'
AWS_REGION: 'us-west-2'
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index afc33db..9261260 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -14,7 +14,7 @@ builds:
- windows
- darwin
ldflags:
- - -s -w -X main.version=v1.3.0
+ - -s -w -X main.version=v1.3.1
archives:
- format: tar.gz
diff --git a/Dockerfile b/Dockerfile
index f340209..58c5754 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# build 1
-FROM golang:1.24.2-alpine3.21 AS build
+FROM golang:1.24.4-alpine3.21 AS build
WORKDIR /app
@@ -8,7 +8,7 @@ RUN go mod download
COPY . .
-RUN go build -ldflags "-X main.version=v1.3.0" -o poke-cli .
+RUN go build -ldflags "-X main.version=v1.3.1" -o poke-cli .
# build 2
FROM --platform=$BUILDPLATFORM alpine:latest
diff --git a/README.md b/README.md
index d465baa..d2aca96 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Pokémon CLI
-
+
@@ -76,11 +76,11 @@ View future plans in the [Roadmap](#roadmap) section.
3. Choose how to interact with the container:
* Run a single command and exit:
```bash
- docker run --rm -it digitalghostdev/poke-cli:v1.3.0 [subcommand] flag]
+ docker run --rm -it digitalghostdev/poke-cli:v1.3.1 [subcommand] flag]
```
* Enter the container and use its shell:
```bash
- docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.3.0 -c "cd /app && exec sh"
+ docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.3.1 -c "cd /app && exec sh"
# placed into the /app directory, run the program with './poke-cli'
# example: ./poke-cli ability swift-swim
```
diff --git a/cli.go b/cli.go
index 5972575..827a08d 100644
--- a/cli.go
+++ b/cli.go
@@ -94,23 +94,28 @@ func runCLI(args []string) int {
return 2
}
- commands := map[string]func(){
+ remainingArgs := mainFlagSet.Args()
+
+ commands := map[string]func() int{
"ability": utils.HandleCommandOutput(ability.AbilityCommand),
"move": utils.HandleCommandOutput(move.MoveCommand),
"natures": utils.HandleCommandOutput(natures.NaturesCommand),
"pokemon": utils.HandleCommandOutput(pokemon.PokemonCommand),
"types": utils.HandleCommandOutput(types.TypesCommand),
- "search": search.SearchCommand,
+ "search": func() int {
+ search.SearchCommand()
+ return 0
+ },
}
cmdArg := ""
- if len(os.Args) >= 2 {
- cmdArg = os.Args[1]
+ if len(remainingArgs) >= 1 {
+ cmdArg = remainingArgs[0]
}
cmdFunc, exists := commands[cmdArg]
switch {
- case len(os.Args) < 2:
+ case len(remainingArgs) == 0 && !*latestFlag && !*shortLatestFlag && !*currentVersionFlag && !*shortCurrentVersionFlag:
mainFlagSet.Usage()
return 1
case *latestFlag || *shortLatestFlag:
@@ -120,13 +125,11 @@ func runCLI(args []string) int {
currentVersion()
return 0
case exists:
- cmdFunc()
- return 0
+ return cmdFunc()
default:
- command := os.Args[1]
errMessage := styling.ErrorBorder.Render(
styling.ErrorColor.Render("Error!"),
- fmt.Sprintf("\n\t%-15s", fmt.Sprintf("'%s' is not a valid command.\n", command)),
+ fmt.Sprintf("\n\t%-15s", fmt.Sprintf("'%s' is not a valid command.\n", cmdArg)),
styling.StyleBold.Render("\nCommands:"),
fmt.Sprintf("\n\t%-15s %s", "ability", "Get details about an ability"),
fmt.Sprintf("\n\t%-15s %s", "move", "Get details about a move"),
@@ -138,7 +141,6 @@ func runCLI(args []string) int {
)
output.WriteString(errMessage)
- // This would typically be returned in a function or passed to something else
fmt.Println(output.String())
return 1
diff --git a/cli_test.go b/cli_test.go
index 84b17c2..2a330a9 100644
--- a/cli_test.go
+++ b/cli_test.go
@@ -122,6 +122,32 @@ func TestRunCLI(t *testing.T) {
}
}
+// TODO: finish testing different commands?
+func TestRunCLI_VariousCommands(t *testing.T) {
+ tests := []struct {
+ name string
+ args []string
+ expected int
+ }{
+ //{"Invalid command", []string{"foobar"}, 1},
+ {"Latest flag long", []string{"--latest"}, 0},
+ {"Latest flag short", []string{"-l"}, 0},
+ {"Version flag long", []string{"--version"}, 0},
+ {"Version flag short", []string{"-v"}, 0},
+ //{"Missing Pokémon name", []string{"pokemon"}, 1},
+ //{"Another invalid command", []string{"invalid"}, 1},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ exitCode := runCLI(tt.args)
+ if exitCode != tt.expected {
+ t.Errorf("expected %d, got %d for args %v", tt.expected, exitCode, tt.args)
+ }
+ })
+ }
+}
+
func TestMainFunction(t *testing.T) {
originalExit := exit
defer func() { exit = originalExit }() // Restore original exit after test
diff --git a/cmd/move/move.go b/cmd/move/move.go
index 1fbf17f..6281ad7 100644
--- a/cmd/move/move.go
+++ b/cmd/move/move.go
@@ -44,7 +44,11 @@ func MoveCommand() (string, error) {
endpoint := strings.ToLower(args[0])
moveName := strings.ToLower(args[1])
- moveStruct, moveName, _ := connections.MoveApiCall(endpoint, moveName, connections.APIURL)
+ moveStruct, moveName, err := connections.MoveApiCall(endpoint, moveName, connections.APIURL)
+ if err != nil {
+ output.WriteString(err.Error())
+ return output.String(), err
+ }
moveInfoContainer(&output, moveStruct, moveName)
moveEffectContainer(&output, moveStruct)
diff --git a/cmd/pokemon/pokemon.go b/cmd/pokemon/pokemon.go
index 30a0491..b6bd1bd 100644
--- a/cmd/pokemon/pokemon.go
+++ b/cmd/pokemon/pokemon.go
@@ -65,19 +65,19 @@ func PokemonCommand() (string, error) {
os.Exit(1)
}
- _, pokemonName, pokemonID, pokemonWeight, pokemonHeight, err := connections.PokemonApiCall(endpoint, pokemonName, connections.APIURL)
+ pokemonStruct, pokemonName, err := connections.PokemonApiCall(endpoint, pokemonName, connections.APIURL)
if err != nil {
- fmt.Println(err)
- os.Exit(1)
+ output.WriteString(err.Error())
+ return output.String(), err
}
capitalizedString := cases.Title(language.English).String(strings.ReplaceAll(pokemonName, "-", " "))
// Weight calculation
- weightKilograms := float64(pokemonWeight) / 10
+ weightKilograms := float64(pokemonStruct.Weight) / 10
weightPounds := float64(weightKilograms) * 2.20462
// Height calculation
- heightMeters := float64(pokemonHeight) / 10
+ heightMeters := float64(pokemonStruct.Height) / 10
heightFeet := heightMeters * 3.28084
feet := int(heightFeet)
inches := int(math.Round((heightFeet - float64(feet)) * 12)) // Use math.Round to avoid truncation
@@ -90,7 +90,7 @@ func PokemonCommand() (string, error) {
output.WriteString(fmt.Sprintf(
"Your selected Pokémon: %s\n%s National Pokédex #: %d\n%s Weight: %.1fkg (%.1f lbs)\n%s Height: %.1fm (%d′%02d″)\n",
- capitalizedString, styling.ColoredBullet, pokemonID,
+ capitalizedString, styling.ColoredBullet, pokemonStruct.ID,
styling.ColoredBullet, weightKilograms, weightPounds,
styling.ColoredBullet, heightFeet, feet, inches,
))
diff --git a/cmd/utils/output.go b/cmd/utils/output.go
index 2f74c9c..e942329 100644
--- a/cmd/utils/output.go
+++ b/cmd/utils/output.go
@@ -5,15 +5,17 @@ import (
"os"
)
-// HandleCommandOutput wraps a function that returns (string, error) into a no-arg function
-// that prints the output to stdout or stderr depending on whether an error occurred.
-func HandleCommandOutput(fn func() (string, error)) func() {
- return func() {
+// HandleCommandOutput takes a function that returns (string, error) and wraps it in a no-argument
+// function that writes the returned string to stdout if there's no error, or to stderr if there is.
+// It returns an exit code: 0 on success, 1 on error.
+func HandleCommandOutput(fn func() (string, error)) func() int {
+ return func() int {
output, err := fn()
if err != nil {
fmt.Fprintln(os.Stderr, output)
- return
+ return 1
}
fmt.Println(output)
+ return 0
}
}
diff --git a/cmd/utils/output_test.go b/cmd/utils/output_test.go
index 5bbd3c8..2454850 100644
--- a/cmd/utils/output_test.go
+++ b/cmd/utils/output_test.go
@@ -31,7 +31,9 @@ func TestHandleCommandOutput_Success(t *testing.T) {
return "it worked", nil
}
- output := captureOutput(&os.Stdout, HandleCommandOutput(fn))
+ output := captureOutput(&os.Stdout, func() {
+ HandleCommandOutput(fn)()
+ })
if output != "it worked\n" {
t.Errorf("expected 'it worked\\n', got %q", output)
@@ -43,7 +45,9 @@ func TestHandleCommandOutput_Error(t *testing.T) {
return "something failed", errors.New("error")
}
- output := captureOutput(&os.Stderr, HandleCommandOutput(fn))
+ output := captureOutput(&os.Stderr, func() {
+ HandleCommandOutput(fn)()
+ })
if output != "something failed\n" {
t.Errorf("expected 'something failed\\n', got %q", output)
diff --git a/connections/connection.go b/connections/connection.go
index bd6a3f3..9bf726c 100644
--- a/connections/connection.go
+++ b/connections/connection.go
@@ -81,11 +81,11 @@ func MoveApiCall(endpoint string, moveName string, baseURL string) (structs.Move
return structs.MoveJSONStruct{}, "", fmt.Errorf("%s", errMessage)
}
- return moveStruct, moveName, nil
+ return moveStruct, moveStruct.Name, nil
}
// PokemonApiCall function for calling the pokemon endpoint of the pokeAPI
-func PokemonApiCall(endpoint string, pokemonName string, baseURL string) (structs.PokemonJSONStruct, string, int, int, int, error) {
+func PokemonApiCall(endpoint string, pokemonName string, baseURL string) (structs.PokemonJSONStruct, string, error) {
fullURL := baseURL + endpoint + "/" + pokemonName
var pokemonStruct structs.PokemonJSONStruct
@@ -96,10 +96,10 @@ func PokemonApiCall(endpoint string, pokemonName string, baseURL string) (struct
styling.ErrorColor.Render("Error!"),
"\nPokémon not found.\n\u2022 Perhaps a typo?\n\u2022 Missing a hyphen instead of a space?",
)
- return structs.PokemonJSONStruct{}, "", 0, 0, 0, fmt.Errorf("%s", errMessage)
+ return structs.PokemonJSONStruct{}, "", fmt.Errorf("%s", errMessage)
}
- return pokemonStruct, pokemonStruct.Name, pokemonStruct.ID, pokemonStruct.Weight, pokemonStruct.Height, nil
+ return pokemonStruct, pokemonStruct.Name, nil
}
// TypesApiCall function for calling the type endpoint of the pokeAPI
diff --git a/connections/connection_test.go b/connections/connection_test.go
index de46029..fcb19d4 100644
--- a/connections/connection_test.go
+++ b/connections/connection_test.go
@@ -59,56 +59,114 @@ func TestApiCallSetup(t *testing.T) {
}
func TestAbilityApiCall(t *testing.T) {
- expectedAbility := structs.AbilityJSONStruct{
- Name: "Unaware",
- }
+ t.Run("Successful API call returns expected ability", func(t *testing.T) {
+ expectedAbility := structs.AbilityJSONStruct{
+ Name: "unaware",
+ }
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- err := json.NewEncoder(w).Encode(expectedAbility)
- assert.NoError(t, err, "Expected no error for skipHTTPSCheck")
- }))
- defer ts.Close()
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ err := json.NewEncoder(w).Encode(expectedAbility)
+ assert.NoError(t, err, "Expected no error for encoding response")
+ }))
+ defer ts.Close()
+
+ ability, name, err := AbilityApiCall("/ability", "unaware", ts.URL)
+
+ require.NoError(t, err, "Expected no error on successful API call")
+ assert.Equal(t, expectedAbility, ability, "Expected ability struct does not match")
+ assert.Equal(t, "unaware", name, "Expected ability name does not match")
+ })
+
+ t.Run("Failed API call returns styled error", func(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Simulate API failure (e.g., 404 Not Found)
+ http.Error(w, "Not Found", http.StatusNotFound)
+ }))
+ defer ts.Close()
+
+ ability, _, err := AbilityApiCall("/ability", "non-existent-ability", ts.URL)
+
+ require.Error(t, err, "Expected an error for invalid ability")
+ assert.Equal(t, structs.AbilityJSONStruct{}, ability, "Expected empty ability struct on error")
+
+ assert.Contains(t, err.Error(), "Ability not found", "Expected 'Ability not found' in error message")
+ assert.Contains(t, err.Error(), "Perhaps a typo?", "Expected helpful suggestion in error message")
+ })
+}
+
+func TestMoveApiCall(t *testing.T) {
+ t.Run("Successful API call returns expected move", func(t *testing.T) {
+ expectedMove := structs.MoveJSONStruct{
+ Name: "shadow-ball",
+ }
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ err := json.NewEncoder(w).Encode(expectedMove)
+ assert.NoError(t, err, "Expected no error for encoding response")
+ }))
+ defer ts.Close()
+
+ move, name, err := MoveApiCall("/move", "shadow-ball", ts.URL)
+
+ require.NoError(t, err, "Expected no error on successful API call")
+ assert.Equal(t, expectedMove, move, "Expected move struct does not match")
+ assert.Equal(t, "shadow-ball", name, "Expected move name does not match")
+ })
+
+ t.Run("Failed API call returns styled error", func(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Simulate API failure (e.g., 404 Not Found)
+ http.Error(w, "Not Found", http.StatusNotFound)
+ }))
+ defer ts.Close()
+
+ move, _, err := MoveApiCall("/move", "non-existent-move", ts.URL)
- _, name, _ := AbilityApiCall("/ability", "unaware", ts.URL)
+ require.Error(t, err, "Expected an error for invalid move")
+ assert.Equal(t, structs.MoveJSONStruct{}, move, "Expected empty move struct on error")
- assert.Equal(t, "Unaware", name, "Expected name does not match the response")
+ assert.Contains(t, err.Error(), "Move not found", "Expected 'Move not found' in error message")
+ assert.Contains(t, err.Error(), "Perhaps a typo?", "Expected helpful suggestion in error message")
+ })
}
func TestPokemonApiCall(t *testing.T) {
- expectedPokemon := structs.PokemonJSONStruct{
- Name: "pikachu",
- ID: 25,
- Weight: 60,
- Height: 4,
- Types: []struct {
- Slot int `json:"slot"`
- Type struct {
- Name string `json:"name"`
- URL string `json:"url"`
- } `json:"type"`
- }{
- {Slot: 1, Type: struct {
- Name string `json:"name"`
- URL string `json:"url"`
- }{Name: "electric", URL: "https://pokeapi.co/api/v2/type/13/"}},
- },
- }
+ t.Run("Successful API call returns expected pokemon", func(t *testing.T) {
+ expectedPokemon := structs.PokemonJSONStruct{
+ Name: "flareon",
+ }
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- err := json.NewEncoder(w).Encode(expectedPokemon)
- assert.NoError(t, err, "Expected no error for skipHTTPSCheck")
- }))
- defer ts.Close()
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ err := json.NewEncoder(w).Encode(expectedPokemon)
+ assert.NoError(t, err, "Expected no error for encoding response")
+ }))
+ defer ts.Close()
- pokemonStruct, name, id, weight, height, _ := PokemonApiCall("/pokemon", "pikachu", ts.URL)
+ pokemon, name, err := PokemonApiCall("/pokemon", "flareon", ts.URL)
+
+ require.NoError(t, err, "Expected no error on successful API call")
+ assert.Equal(t, expectedPokemon, pokemon, "Expected pokemon struct does not match")
+ assert.Equal(t, "flareon", name, "Expected pokemon name does not match")
+ })
- assert.Equal(t, expectedPokemon, pokemonStruct, "Expected Pokémon struct does not match")
- assert.Equal(t, "pikachu", name, "Expected name does not match")
- assert.Equal(t, 25, id, "Expected ID does not match")
- assert.Equal(t, 60, weight, "Expected weight does not match")
- assert.Equal(t, 4, height, "Expected height does not match")
+ t.Run("Failed API call returns styled error", func(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Simulate API failure (e.g., 404 Not Found)
+ http.Error(w, "Not Found", http.StatusNotFound)
+ }))
+ defer ts.Close()
+
+ pokemon, _, err := PokemonApiCall("/pokemon", "non-existent-pokemon", ts.URL)
+
+ require.Error(t, err, "Expected an error for invalid pokemon")
+ assert.Equal(t, structs.PokemonJSONStruct{}, pokemon, "Expected empty pokemon struct on error")
+
+ assert.Contains(t, err.Error(), "Pokémon not found", "Expected 'Pokémon not found' in error message")
+ assert.Contains(t, err.Error(), "Perhaps a typo?", "Expected helpful suggestion in error message")
+ })
}
// TestTypesApiCall - Test for the TypesApiCall function
diff --git a/flags/pokemonflagset.go b/flags/pokemonflagset.go
index 4d4b0a5..45d17dc 100644
--- a/flags/pokemonflagset.go
+++ b/flags/pokemonflagset.go
@@ -75,7 +75,7 @@ func SetupPokemonFlagSet() (*flag.FlagSet, *bool, *bool, *string, *string, *bool
}
func AbilitiesFlag(w io.Writer, endpoint string, pokemonName string) error {
- pokemonStruct, _, _, _, _, _ := connections.PokemonApiCall(endpoint, pokemonName, connections.APIURL)
+ pokemonStruct, _, _ := connections.PokemonApiCall(endpoint, pokemonName, connections.APIURL)
// Print the header from header func
_, err := fmt.Fprintln(w, header("Abilities"))
@@ -129,7 +129,7 @@ func AbilitiesFlag(w io.Writer, endpoint string, pokemonName string) error {
func ImageFlag(w io.Writer, endpoint string, pokemonName string, size string) error {
baseURL := "https://pokeapi.co/api/v2/"
- pokemonStruct, _, _, _, _, _ := connections.PokemonApiCall(endpoint, pokemonName, baseURL)
+ pokemonStruct, _, _ := connections.PokemonApiCall(endpoint, pokemonName, baseURL)
// Print the header from header func
_, err := fmt.Fprintln(w, header("Image"))
@@ -208,7 +208,7 @@ func ImageFlag(w io.Writer, endpoint string, pokemonName string, size string) er
func MovesFlag(w io.Writer, endpoint string, pokemonName string) error {
baseURL := "https://pokeapi.co/api/v2/"
- pokemonStruct, _, _, _, _, _ := connections.PokemonApiCall(endpoint, pokemonName, baseURL)
+ pokemonStruct, _, _ := connections.PokemonApiCall(endpoint, pokemonName, baseURL)
_, err := fmt.Fprintln(w, header("Learnable Moves"))
if err != nil {
@@ -319,7 +319,7 @@ func MovesFlag(w io.Writer, endpoint string, pokemonName string) error {
func StatsFlag(w io.Writer, endpoint string, pokemonName string) error {
baseURL := "https://pokeapi.co/api/v2/"
- pokemonStruct, _, _, _, _, _ := connections.PokemonApiCall(endpoint, pokemonName, baseURL)
+ pokemonStruct, _, _ := connections.PokemonApiCall(endpoint, pokemonName, baseURL)
// Print the header from header func
_, err := fmt.Fprintln(w, header("Base Stats"))
@@ -415,7 +415,7 @@ func StatsFlag(w io.Writer, endpoint string, pokemonName string) error {
func TypesFlag(w io.Writer, endpoint string, pokemonName string) error {
baseURL := "https://pokeapi.co/api/v2/"
- pokemonStruct, _, _, _, _, _ := connections.PokemonApiCall(endpoint, pokemonName, baseURL)
+ pokemonStruct, _, _ := connections.PokemonApiCall(endpoint, pokemonName, baseURL)
// Print the header from header func
_, err := fmt.Fprintln(w, header("Typing"))
diff --git a/flags/version.go b/flags/version.go
index 0e07587..5630996 100644
--- a/flags/version.go
+++ b/flags/version.go
@@ -11,24 +11,22 @@ import (
"runtime"
)
-func latestDockerImage() {
+type commandRunner func(name string, args ...string) *exec.Cmd
+
+func latestDockerImage(run commandRunner) {
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
- // Windows PowerShell equivalent
- cmd = exec.Command("powershell", "-Command", `
- $tags = Invoke-RestMethod -Uri "https://hub.docker.com/v2/repositories/digitalghostdev/poke-cli/tags/?page_size=1";
- $tags.results[0].name
- `)
+ cmd = run("powershell", "-Command", `
+ $tags = Invoke-RestMethod -Uri "https://hub.docker.com/v2/repositories/digitalghostdev/poke-cli/tags/?page_size=1";
+ $tags.results[0].name
+ `)
} else {
- // Check if curl is available
_, err := exec.LookPath("curl")
if err == nil {
- // Use curl if available
- cmd = exec.Command("sh", "-c", `curl -s https://hub.docker.com/v2/repositories/digitalghostdev/poke-cli/tags/?page_size=1 | grep -o '"name":"[^"]*"' | cut -d '"' -f 4`)
+ cmd = run("sh", "-c", `curl -s https://hub.docker.com/v2/repositories/digitalghostdev/poke-cli/tags/?page_size=1 | grep -o '"name":"[^"]*"' | cut -d '"' -f 4`)
} else {
- // Use wget as a fallback
- cmd = exec.Command("sh", "-c", `wget -qO- https://hub.docker.com/v2/repositories/digitalghostdev/poke-cli/tags/?page_size=1 | grep -o '"name":"[^"]*"' | cut -d '"' -f 4`)
+ cmd = run("sh", "-c", `wget -qO- https://hub.docker.com/v2/repositories/digitalghostdev/poke-cli/tags/?page_size=1 | grep -o '"name":"[^"]*"' | cut -d '"' -f 4`)
}
}
@@ -90,6 +88,6 @@ func latestRelease(githubAPIURL string) {
func LatestFlag() {
// cmd := exec.Command("git", "describe", "--tags", "--abbrev=0")
- latestDockerImage()
+ latestDockerImage(exec.Command)
latestRelease("https://api.github.com/repos/digitalghost-dev/poke-cli/releases/latest")
}
diff --git a/flags/version_test.go b/flags/version_test.go
index 28538cc..036424a 100644
--- a/flags/version_test.go
+++ b/flags/version_test.go
@@ -7,7 +7,7 @@ import (
"net/http"
"net/http/httptest"
"os"
- "runtime"
+ "os/exec"
"testing"
)
@@ -36,17 +36,38 @@ func captureOutput(f func()) string {
}
func TestLatestDockerImage(t *testing.T) {
- output := captureOutput(func() { latestDockerImage() })
-
- assert.Contains(t, output, "Latest Docker image version:")
+ tests := []struct {
+ name string
+ mockRunner func(name string, args ...string) *exec.Cmd
+ expectError bool
+ expectText string
+ }{
+ {
+ name: "success",
+ mockRunner: func(name string, args ...string) *exec.Cmd {
+ return exec.Command("echo", "v1.0.0\n")
+ },
+ expectError: false,
+ expectText: "Latest Docker image version: v1.0.0",
+ },
+ {
+ name: "error from command",
+ mockRunner: func(name string, args ...string) *exec.Cmd {
+ return exec.Command("false") // returns error
+ },
+ expectError: true,
+ expectText: "Error running command",
+ },
+ }
- // Since the actual API response might change, avoid testing the exact version number.
- // Instead, check if a non-empty version string is printed.
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ output := captureOutput(func() {
+ latestDockerImage(tt.mockRunner)
+ })
- if runtime.GOOS == "windows" {
- assert.Contains(t, output, "\n")
- } else {
- assert.Contains(t, output, "\n")
+ assert.Contains(t, output, tt.expectText)
+ })
}
}
diff --git a/go.mod b/go.mod
index 8855bc6..1a6c1bd 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/digitalghost-dev/poke-cli
-go 1.24.1
+go 1.24.4
require (
github.com/charmbracelet/bubbles v0.20.0