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 @@ pokemon-logo

Pokémon CLI

version-label - docker-image-size + docker-image-size ci-status-badge
@@ -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