diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7605ebd..2818d3c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,7 +21,7 @@ on:
branches:
- main
env:
- VERSION_NUMBER: 'v0.7.1'
+ VERSION_NUMBER: 'v0.7.2'
REGISTRY_NAME: digitalghostdev/poke-cli
jobs:
diff --git a/README.md b/README.md
index e726bb6..f2e9f24 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Pokémon CLI
-
+
@@ -40,7 +40,7 @@ _Taskfile can build the executable for you_
_Use a Docker Image_
```bash
-docker run --rm -it digitalghostdev/poke-cli:v0.7.1 [command] [subcommand] [flag]
+docker run --rm -it digitalghostdev/poke-cli:v0.7.2 [command] [subcommand] [flag]
```
### Go Build
diff --git a/cli_test.go b/cli_test.go
index 22241bb..e797a2e 100644
--- a/cli_test.go
+++ b/cli_test.go
@@ -2,67 +2,63 @@ package main
import (
"bytes"
- "fmt"
- "io"
"os"
"regexp"
"testing"
)
-// Strip ANSI color codes
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)
-func stripANSI(input string) string {
- return ansiRegex.ReplaceAllString(input, "")
-}
+func captureOutput(f func()) string {
+ var buf bytes.Buffer
+ stdout := os.Stdout
+ r, w, _ := os.Pipe()
+ os.Stdout = w
-func TestMainFunction(t *testing.T) {
- version := "v0.7.0"
+ f()
- // Backup the original exit function and stdout/stderr
- originalExit := exit
- originalStdout := os.Stdout
- originalStderr := os.Stderr
- defer func() {
- exit = originalExit // Restore exit
- os.Stdout = originalStdout // Restore stdout
- os.Stderr = originalStderr // Restore stderr
- }()
+ _ = w.Close()
+ os.Stdout = stdout
+ _, _ = buf.ReadFrom(r)
- // Replace exit with a function that captures the exit code
- exitCode := 0
- exit = func(code int) { exitCode = code }
+ return buf.String()
+}
+func stripANSI(input string) string {
+ return ansiRegex.ReplaceAllString(input, "")
+}
+
+func TestRunCLI(t *testing.T) {
tests := []struct {
+ name string
args []string
expectedOutput string
- expectedExit int
+ expectedCode int
}{
{
- args: []string{"pokemons"},
+ name: "No Arguments",
+ args: []string{},
expectedOutput: "╭──────────────────────────────────────────────────────╮\n" +
- "│Error! │\n" +
- "│ 'pokemons' is not a valid command. │\n" +
+ "│Welcome! This tool displays data related to Pokémon! │\n" +
"│ │\n" +
- "│Available Commands: │\n" +
+ "│ USAGE: │\n" +
+ "│ poke-cli [flag] │\n" +
+ "│ poke-cli [flag] │\n" +
+ "│ poke-cli [flag] │\n" +
+ "│ │\n" +
+ "│ FLAGS: │\n" +
+ "│ -h, --help Shows the help menu │\n" +
+ "│ -l, --latest Prints the latest available │\n" +
+ "│ version of the program │\n" +
+ "│ │\n" +
+ "│ AVAILABLE COMMANDS: │\n" +
"│ pokemon Get details of a specific Pokémon │\n" +
"│ types Get details of a specific typing │\n" +
- "│ │\n" +
- "│Also run [poke-cli -h] for more info! │\n" +
"╰──────────────────────────────────────────────────────╯\n",
- expectedExit: 1,
- },
- {
- args: []string{"-l"},
- expectedOutput: fmt.Sprintf("Latest Docker image version: %s\nLatest release tag: %s\n", version, version),
- expectedExit: 0,
- },
- {
- args: []string{"--latest"},
- expectedOutput: fmt.Sprintf("Latest Docker image version: %s\nLatest release tag: %s\n", version, version),
- expectedExit: 0,
+ expectedCode: 0,
},
{
+ name: "Help Flag Short",
args: []string{"-h"},
expectedOutput: "╭──────────────────────────────────────────────────────╮\n" +
"│Welcome! This tool displays data related to Pokémon! │\n" +
@@ -81,64 +77,59 @@ func TestMainFunction(t *testing.T) {
"│ pokemon Get details of a specific Pokémon │\n" +
"│ types Get details of a specific typing │\n" +
"╰──────────────────────────────────────────────────────╯\n",
- expectedExit: 0,
- },
- {
- args: []string{"pokemon", "kingambit"},
- expectedOutput: "Your selected Pokémon: Kingambit\nNational Pokédex #: 983\n",
- expectedExit: 0,
+ expectedCode: 0,
},
{
- args: []string{"pokemon", "cradily", "--types"},
- expectedOutput: "Your selected Pokémon: Cradily\nNational Pokédex #: 346\n──────\nTyping\nType 1: rock\nType 2: grass\n",
- expectedExit: 0,
+ name: "Help Flag Long",
+ args: []string{"--help"},
+ expectedOutput: "╭──────────────────────────────────────────────────────╮\n" +
+ "│Welcome! This tool displays data related to Pokémon! │\n" +
+ "│ │\n" +
+ "│ USAGE: │\n" +
+ "│ poke-cli [flag] │\n" +
+ "│ poke-cli [flag] │\n" +
+ "│ poke-cli [flag] │\n" +
+ "│ │\n" +
+ "│ FLAGS: │\n" +
+ "│ -h, --help Shows the help menu │\n" +
+ "│ -l, --latest Prints the latest available │\n" +
+ "│ version of the program │\n" +
+ "│ │\n" +
+ "│ AVAILABLE COMMANDS: │\n" +
+ "│ pokemon Get details of a specific Pokémon │\n" +
+ "│ types Get details of a specific typing │\n" +
+ "╰──────────────────────────────────────────────────────╯\n",
+ expectedCode: 0,
},
{
- args: []string{"pokemon", "giratina-altered", "--abilities"},
- expectedOutput: "Your selected Pokémon: Giratina-Altered\nNational Pokédex #: 487\n─────────\nAbilities\nAbility 1: pressure\nHidden Ability: telepathy\n",
- expectedExit: 0,
+ name: "Invalid Command",
+ args: []string{"invalid"},
+ expectedOutput: "Error!",
+ expectedCode: 1,
},
{
- args: []string{"pokemon", "coPPeraJAH", "-t", "-a"},
- expectedOutput: "Your selected Pokémon: Copperajah\nNational Pokédex #: 879\n──────\nTyping\nType 1: steel\n─────────\nAbilities\nAbility 1: sheer-force\nHidden Ability: heavy-metal\n",
- expectedExit: 0,
+ name: "Latest Flag",
+ args: []string{"-l"},
+ expectedOutput: "Latest Docker image version: v0.7.1\nLatest release tag: v0.7.1\n",
+ expectedCode: 0,
},
}
- for _, test := range tests {
- // Create a pipe to capture output
- r, w, _ := os.Pipe()
- os.Stdout = w
- os.Stderr = w
-
- // Set os.Args for the test case
- os.Args = append([]string{"poke-cli"}, test.args...)
-
- // Run the main function
- main()
-
- // Close the writer and restore stdout and stderr
- err := w.Close()
- if err != nil {
- t.Fatalf("Error closing pipe writer: %v", err)
- }
- os.Stdout = originalStdout
- os.Stderr = originalStderr
-
- // Read from the pipe
- var buf bytes.Buffer
- if _, err := io.Copy(&buf, r); err != nil {
- t.Errorf("Error copying output: %v", err)
- }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ exit = func(code int) {}
+ output := captureOutput(func() {
+ code := runCLI(tt.args)
+ if code != tt.expectedCode {
+ t.Errorf("Expected exit code %d, got %d", tt.expectedCode, code)
+ }
+ })
- // Strip ANSI color codes from the actual output
- actualOutput := stripANSI(buf.String())
- if actualOutput != test.expectedOutput {
- t.Errorf("Args: %v\nExpected output: %q\nGot: %q\n", test.args, test.expectedOutput, actualOutput)
- }
+ output = stripANSI(output)
- if exitCode != test.expectedExit {
- t.Errorf("Args: %v\nExpected exit code: %d\nGot: %d\n", test.args, test.expectedExit, exitCode)
- }
+ if !bytes.Contains([]byte(output), []byte(tt.expectedOutput)) {
+ t.Errorf("Expected output to contain %q, got %q", tt.expectedOutput, output)
+ }
+ })
}
}
diff --git a/cmd/pokemon_test.go b/cmd/pokemon_test.go
index ca823dc..7eb5140 100644
--- a/cmd/pokemon_test.go
+++ b/cmd/pokemon_test.go
@@ -1,7 +1,9 @@
package cmd
import (
+ "bytes"
"github.com/stretchr/testify/assert"
+ "os"
"testing"
)
@@ -49,3 +51,38 @@ func TestValidatePokemonArgs_TooManyArgs(t *testing.T) {
assert.NotEqual(t, expectedError, err.Error())
}
}
+
+func TestPokemonCommand(t *testing.T) {
+ // Capture standard output
+ var output bytes.Buffer
+ stdout := os.Stdout
+ r, w, _ := os.Pipe()
+ os.Stdout = w
+
+ // Set up test arguments (focus only on Pokémon name and command)
+ os.Args = []string{"poke-cli", "pokemon", "bulbasaur"}
+
+ // Call the function
+ PokemonCommand()
+
+ // Close and restore stdout
+ if closeErr := w.Close(); closeErr != nil {
+ t.Fatalf("Failed to close pipe writer: %v", closeErr)
+ }
+ os.Stdout = stdout
+
+ _, readErr := output.ReadFrom(r)
+ if readErr != nil {
+ t.Fatalf("Failed to read from pipe: %v", readErr)
+ }
+
+ // Define expected output based on actual API response
+ expectedName := "Bulbasaur"
+ expectedID := "1"
+
+ // Assert output contains expected Pokémon details
+ assert.Contains(t, output.String(), "Your selected Pokémon:", "Output should contain Pokémon details header")
+ assert.Contains(t, output.String(), expectedName, "Output should contain the Pokémon name")
+ assert.Contains(t, output.String(), "National Pokédex #:", "Output should contain Pokémon ID header")
+ assert.Contains(t, output.String(), expectedID, "Output should contain the Pokémon ID")
+}
diff --git a/cmd/styles.go b/cmd/styles.go
index 3f5dffb..9435520 100644
--- a/cmd/styles.go
+++ b/cmd/styles.go
@@ -38,3 +38,10 @@ var (
"fairy": "#EE99EE",
}
)
+
+// Helper function to get color for a given type name from colorMap
+func getTypeColor(typeName string) string {
+ color := colorMap[typeName]
+
+ return color
+}
diff --git a/cmd/styles_test.go b/cmd/styles_test.go
new file mode 100644
index 0000000..c959eab
--- /dev/null
+++ b/cmd/styles_test.go
@@ -0,0 +1,22 @@
+package cmd
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestGetTypeColor(t *testing.T) {
+ // Test known types
+ for typeName, expectedColor := range colorMap {
+ t.Run(typeName, func(t *testing.T) {
+ color := getTypeColor(typeName)
+ assert.Equal(t, expectedColor, color, "Expected color for type %s to be %s", typeName, expectedColor)
+ })
+ }
+
+ // Test unknown type
+ t.Run("unknown type", func(t *testing.T) {
+ color := getTypeColor("unknown")
+ assert.Equal(t, "", color, "Expected color for unknown type to be an empty string")
+ })
+}
diff --git a/cmd/types.go b/cmd/types.go
index b004e79..dd28069 100644
--- a/cmd/types.go
+++ b/cmd/types.go
@@ -51,13 +51,6 @@ func (m model) View() string {
return "Select a type! Hit 'Q' or 'CTRL-C' to quit.\n" + typesTableBorder.Render(m.table.View()) + "\n"
}
-// Helper function to get color for a given type name from colorMap
-func getTypeColor(typeName string) string {
- color := colorMap[typeName]
-
- return color
-}
-
// Function to display type details after a type is selected
func displayTypeDetails(typesName string, endpoint string) {
diff --git a/cmd/types_test.go b/cmd/types_test.go
index dc2baca..7ec34a1 100644
--- a/cmd/types_test.go
+++ b/cmd/types_test.go
@@ -29,3 +29,27 @@ func TestValidateTypesArgs_TooManyArgs(t *testing.T) {
assert.NotEqual(t, expectedError, err.Error())
}
}
+
+func TestModelInit(t *testing.T) {
+ m := model{}
+ result := m.Init()
+
+ assert.Nil(t, result, "Expected Init() to return nil")
+}
+
+func TestModelView_SelectedOption(t *testing.T) {
+ m := model{selectedOption: "someOption"}
+
+ output := m.View()
+
+ assert.Equal(t, "", output, "Expected output to be an empty string when selectedOption is set")
+}
+
+func TestModelView_DisplayTable(t *testing.T) {
+ m := model{selectedOption: ""}
+ expectedOutput := "Select a type! Hit 'Q' or 'CTRL-C' to quit.\n" + typesTableBorder.Render(m.table.View()) + "\n"
+
+ output := m.View()
+
+ assert.Equal(t, expectedOutput, output, "Expected View output to include table view")
+}
diff --git a/connections/connection.go b/connections/connection.go
index d61126e..fab11e8 100644
--- a/connections/connection.go
+++ b/connections/connection.go
@@ -68,13 +68,12 @@ type TypesJSONStruct struct {
} `json:"damage_relations"`
}
-var httpGet = http.Get
var red = lipgloss.Color("#F2055C")
var errorColor = lipgloss.NewStyle().Foreground(red)
// ApiCallSetup Helper function to handle API calls and JSON unmarshalling
func ApiCallSetup(url string, target interface{}) error {
- res, err := httpGet(url)
+ res, err := http.Get(url)
if err != nil {
return fmt.Errorf("error making GET request: %w", err)
}
diff --git a/flags/pokemonflagset_test.go b/flags/pokemonflagset_test.go
index 685ab23..aaa1569 100644
--- a/flags/pokemonflagset_test.go
+++ b/flags/pokemonflagset_test.go
@@ -1,7 +1,9 @@
package flags
import (
+ "bytes"
"github.com/stretchr/testify/assert"
+ "os"
"testing"
)
@@ -30,3 +32,76 @@ func TestSetupPokemonFlagSet(t *testing.T) {
assert.NotNil(t, shortAbilitiesFlag, "Short abilities flag should not be nil")
assert.Equal(t, false, *shortAbilitiesFlag, "Short abilities flag name should be 'a'")
}
+
+func TestAbilitiesFlag(t *testing.T) {
+ // Capture standard output
+ var output bytes.Buffer
+ stdout := os.Stdout
+ r, w, _ := os.Pipe()
+ os.Stdout = w
+
+ // Call the function with a known Pokémon (e.g., bulbasaur)
+ err := AbilitiesFlag("pokemon", "bulbasaur")
+
+ // Close and restore stdout
+ if closeErr := w.Close(); closeErr != nil {
+ t.Fatalf("Failed to close pipe writer: %v", closeErr)
+ }
+ os.Stdout = stdout
+
+ _, readErr := output.ReadFrom(r)
+ if readErr != nil {
+ t.Fatalf("Failed to read from pipe: %v", readErr)
+ }
+
+ // Assert no errors occurred during execution
+ assert.NoError(t, err)
+
+ // Define the expected output based on the API response
+ expectedOutput := `─────────
+Abilities
+Ability 1: overgrow
+Hidden Ability: chlorophyll
+`
+
+ // Assert the actual output matches the expected output
+ assert.Contains(t, output.String(), "Abilities", "Output should contain 'Abilities'")
+ assert.Contains(t, output.String(), "Ability 1: overgrow", "Output should contain 'Ability 1: overgrow'")
+ assert.Contains(t, output.String(), "Hidden Ability: chlorophyll", "Output should contain 'Ability 2: chlorophyll'")
+ assert.Equal(t, expectedOutput, output.String(), "Output does not match the expected formatting")
+}
+
+func TestTypesFlag(t *testing.T) {
+ // Capture standard output
+ var output bytes.Buffer
+ stdout := os.Stdout
+ r, w, _ := os.Pipe()
+ os.Stdout = w
+
+ // Call the TypesFlag function with a valid Pokémon
+ err := TypesFlag("pokemon", "bulbasaur")
+
+ // Close and restore stdout
+ if closeErr := w.Close(); closeErr != nil {
+ t.Fatalf("Failed to close pipe writer: %v", closeErr)
+ }
+ os.Stdout = stdout
+
+ _, readErr := output.ReadFrom(r)
+ if readErr != nil {
+ t.Fatalf("Failed to read from pipe: %v", readErr)
+ }
+
+ // Assert no errors occurred
+ assert.NoError(t, err, "TypesFlag should not return an error for a valid Pokémon")
+
+ // Define expected output components
+ expectedHeader := "Typing"
+ expectedType1 := "Type 1: grass"
+ expectedType2 := "Type 2: poison"
+
+ // Assert output contains the expected header and typing information
+ assert.Contains(t, output.String(), expectedHeader, "Output should contain the 'Typing' header")
+ assert.Contains(t, output.String(), expectedType1, "Output should contain the Pokémon's first type")
+ assert.Contains(t, output.String(), expectedType2, "Output should contain the Pokémon's second type")
+}
diff --git a/flags/version.go b/flags/version.go
index 5f4ebe0..4d20342 100644
--- a/flags/version.go
+++ b/flags/version.go
@@ -8,14 +8,12 @@ import (
"os/exec"
)
-func latestDockerImage() {
- fullCommand := `curl -s https://hub.docker.com/v2/repositories/digitalghostdev/poke-cli/tags/?page_size=1 | grep -o '"name":"[^"]*"' | cut -d '"' -f 4`
-
+func latestDockerImage(fullCommand string) {
cmd := exec.Command("bash", "-c", fullCommand)
-
output, err := cmd.Output()
if err != nil {
- fmt.Printf("error running 'command': %v\n", err)
+ fmt.Printf("error running command: %v\n", err)
+ return
}
fmt.Print("Latest Docker image version: ", string(output))
@@ -55,6 +53,6 @@ func latestRelease(githubAPIURL string) {
func LatestFlag() {
// cmd := exec.Command("git", "describe", "--tags", "--abbrev=0")
- latestDockerImage()
+ latestDockerImage(`curl -s https://hub.docker.com/v2/repositories/digitalghostdev/poke-cli/tags/?page_size=1 | grep -o '"name":"[^"]*"' | cut -d '"' -f 4`)
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 fa06209..c1bb59b 100644
--- a/flags/version_test.go
+++ b/flags/version_test.go
@@ -22,7 +22,10 @@ func captureOutput(f func()) string {
f()
// Restore the original stdout and close the writer
- w.Close()
+ err := w.Close()
+ if err != nil {
+ return fmt.Sprintf("error closing writer: %v", err)
+ }
os.Stdout = oldStdout
// Read the captured output
@@ -32,10 +35,15 @@ func captureOutput(f func()) string {
}
func TestLatestDockerImage(t *testing.T) {
- output := captureOutput(latestDockerImage)
-
- // Modify this assertion as needed based on expected output
+ // Test normal execution with a valid command
+ validCommand := `curl -s https://hub.docker.com/v2/repositories/digitalghostdev/poke-cli/tags/?page_size=1 | grep -o '"name":"[^"]*"' | cut -d '"' -f 4`
+ output := captureOutput(func() { latestDockerImage(validCommand) })
assert.Contains(t, output, "Latest Docker image version:")
+
+ // Test command failure with an invalid command
+ invalidCommand := "invalidcommand"
+ output = captureOutput(func() { latestDockerImage(invalidCommand) })
+ assert.Contains(t, output, "error running command:")
}
func TestLatestRelease(t *testing.T) {
@@ -48,7 +56,11 @@ func TestLatestRelease(t *testing.T) {
func TestLatestRelease_Success(t *testing.T) {
// Create a mock server that simulates a successful response
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, `{"tag_name": "v1.0.0"}`)
+ _, err := fmt.Fprintln(w, `{"tag_name": "v1.0.0"}`)
+ if err != nil {
+ t.Errorf("failed to write response: %v", err)
+ return
+ }
})
server := httptest.NewServer(handler)
defer server.Close()
@@ -61,7 +73,11 @@ func TestLatestRelease_Success(t *testing.T) {
func TestLatestRelease_InvalidJSON(t *testing.T) {
// Create a mock server that returns invalid JSON
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, `invalid-json`)
+ _, err := fmt.Fprintln(w, `invalid-json`)
+ if err != nil {
+ t.Errorf("failed to write response: %v", err)
+ return
+ }
})
server := httptest.NewServer(handler)
defer server.Close()