Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Run tests
id: test
run: |
go test -v ./tests/... 2>&1 | tee test-output.txt
go test -v ./... 2>&1 | tee test-output.txt
echo "test_exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT

- name: Comment PR with test results
Expand Down
173 changes: 173 additions & 0 deletions cmd/commands_part1_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package cmd

import (
"bytes"
"os"
"strings"
"testing"

"github.com/spf13/cobra"
)

// Helper to capture stdout
func captureStdout(f func()) string {
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

f()

w.Close()
os.Stdout = oldStdout

var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}

func setupCmdTest(t *testing.T) {
// Setup DB before running tests
tempDir, err := os.MkdirTemp("", "dave_cmd_test_*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}

originalHome := os.Getenv("HOME")
os.Setenv("HOME", tempDir)

t.Cleanup(func() {
os.RemoveAll(tempDir)
os.Setenv("HOME", originalHome)
cleanupDatabase()
db = nil
})

initDatabase()
}

func executeCommand(args []string) string {
return captureStdout(func() {
rootCmd.SetArgs(args)
rootCmd.Execute()
})
}

func executeRawCommand(cmdToRun *cobra.Command) string {
return captureStdout(func() {
// When running directly, we don't have args if we don't supply them
cmdToRun.Run(cmdToRun, []string{})
})
}

func TestShowCmd(t *testing.T) {
setupCmdTest(t)

output := executeCommand([]string{"show"})
if !strings.Contains(output, "No debts tracked") {
t.Errorf("Expected show cmd to print 'No debts tracked', got %v", output)
}
}

func TestAddCmd(t *testing.T) {
setupCmdTest(t)

// Valid add
output := executeCommand([]string{"add", "Card A", "1000", "15.0", "100"})
if !strings.Contains(output, "Added Card A") {
t.Errorf("Expected successful add message, got %v", output)
}

// Test errors
errorTests := []struct {
args []string
expectedErr string
}{
{[]string{"add", "", "1000", "15.0", "100"}, "Creditor name cannot be empty"},
{[]string{"add", "Card B", "invalid", "15.0", "100"}, "Balance must be a positive number"},
// Negative values will be treated as flags by cobra, so we need `--`
{[]string{"add", "--", "Card B", "-100", "15.0", "100"}, "Balance must be a positive number"},
{[]string{"add", "Card B", "1000", "invalid", "100"}, "APR must be a non-negative number"},
{[]string{"add", "--", "Card B", "1000", "-5", "100"}, "APR must be a non-negative number"},
{[]string{"add", "Card B", "1000", "15.0", "invalid"}, "Payment must be a positive number"},
{[]string{"add", "--", "Card B", "1000", "15.0", "-10"}, "Payment must be a positive number"},
}

for _, tc := range errorTests {
output := executeCommand(tc.args)
if !strings.Contains(output, tc.expectedErr) {
t.Errorf("Expected error '%s', got '%s'", tc.expectedErr, output)
}
}
}

func TestRemoveCmd(t *testing.T) {
setupCmdTest(t)

// Add a debt to remove
executeCommand([]string{"add", "Card To Remove", "1000", "15.0", "100"})

// Valid remove
output := executeCommand([]string{"remove", "Card To Remove"})
if !strings.Contains(output, "Removed Card To Remove") {
t.Errorf("Expected successful remove message, got %v", output)
}

// Empty string remove
output = executeCommand([]string{"remove", " "})
if !strings.Contains(output, "Debt identifier cannot be empty") {
t.Errorf("Expected error message for empty identifier")
}

// Non-existent remove
output = executeCommand([]string{"remove", "Nonexistent"})
if !strings.Contains(output, "Error") {
t.Errorf("Expected error message for non-existent debt")
}
}

func TestResetCmd(t *testing.T) {
setupCmdTest(t)

// Since reset requires user input ("yes"), we need to mock os.Stdin
oldStdin := os.Stdin
r, w, _ := os.Pipe()
os.Stdin = r

// Write "yes\n" to mock stdin
w.Write([]byte("yes\n"))
w.Close()

output := executeRawCommand(resetCmd)

os.Stdin = oldStdin

if !strings.Contains(output, "All debts and payment history have been deleted") {
t.Errorf("Expected reset confirmation, got %v", output)
}

// Test cancellation
r, w, _ = os.Pipe()
os.Stdin = r
w.Write([]byte("no\n"))
w.Close()

output = executeRawCommand(resetCmd)

os.Stdin = oldStdin

if !strings.Contains(output, "Reset cancelled") {
t.Errorf("Expected reset cancellation, got %v", output)
}
}

// Ensure database errors trigger printed errors in ShowCmd
func TestShowCmd_Error(t *testing.T) {
setupCmdTest(t)
db.Close() // Cause error

output := executeRawCommand(showCmd)

if !strings.Contains(output, "Error getting settings") {
t.Errorf("Expected error output, got %v", output)
}
}
107 changes: 107 additions & 0 deletions cmd/commands_part2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package cmd

import (
"strings"
"testing"
)

func TestAdjustAmountCmd(t *testing.T) {
setupCmdTest(t)

// Add debt first
executeCommand([]string{"add", "Card A", "1000", "15.0", "100"})

// Valid adjust
output := executeCommand([]string{"adjust-amount", "Card A", "1200"})
if !strings.Contains(output, "Updated Card A balance to $1200.00") {
t.Errorf("Expected successful adjustment message, got %v", output)
}

// Test errors
errorTests := []struct {
args []string
expectedErr string
}{
{[]string{"adjust-amount", "", "1200"}, "Debt identifier cannot be empty"},
{[]string{"adjust-amount", "Card A", "invalid"}, "Amount must be a non-negative number"},
{[]string{"adjust-amount", "--", "Card A", "-100"}, "Amount must be a non-negative number"},
{[]string{"adjust-amount", "Nonexistent", "1200"}, "Error"},
}

for _, tc := range errorTests {
output := executeCommand(tc.args)
if !strings.Contains(output, tc.expectedErr) {
t.Errorf("Expected error '%s', got '%s'", tc.expectedErr, output)
}
}
}

func TestAdjustRateCmd(t *testing.T) {
setupCmdTest(t)

executeCommand([]string{"add", "Card A", "1000", "15.0", "100"})

// Valid adjust
output := executeCommand([]string{"adjust-rate", "Card A", "12.5"})
if !strings.Contains(output, "Updated Card A rate to 12.50%") {
t.Errorf("Expected successful adjustment message, got %v", output)
}

// Test errors
errorTests := []struct {
args []string
expectedErr string
}{
{[]string{"adjust-rate", "", "12.5"}, "Debt identifier cannot be empty"},
{[]string{"adjust-rate", "Card A", "invalid"}, "Rate must be a non-negative number"},
{[]string{"adjust-rate", "--", "Card A", "-5"}, "Rate must be a non-negative number"},
{[]string{"adjust-rate", "Nonexistent", "12.5"}, "Error"},
}

for _, tc := range errorTests {
output := executeCommand(tc.args)
if !strings.Contains(output, tc.expectedErr) {
t.Errorf("Expected error '%s', got '%s'", tc.expectedErr, output)
}
}
}

func TestAdjustOrderCmd(t *testing.T) {
setupCmdTest(t)

executeCommand([]string{"add", "Card A", "1000", "15.0", "100"})

// Not in manual mode initially
output := executeCommand([]string{"adjust-order", "Card A", "1"})
if !strings.Contains(output, "adjust-order only works in manual sort mode") {
t.Errorf("Expected manual mode error, got %v", output)
}

// Switch to manual mode
executeCommand([]string{"mode", "manual"})

// Valid adjust
output = executeCommand([]string{"adjust-order", "Card A", "5"})
if !strings.Contains(output, "Updated Card A order to 5") {
t.Errorf("Expected successful adjustment message, got %v", output)
}

// Test errors
errorTests := []struct {
args []string
expectedErr string
}{
{[]string{"adjust-order", "", "1"}, "Debt identifier cannot be empty"},
{[]string{"adjust-order", "Card A", "invalid"}, "Order must be a positive integer"},
{[]string{"adjust-order", "--", "Card A", "0"}, "Order must be a positive integer"},
{[]string{"adjust-order", "--", "Card A", "-1"}, "Order must be a positive integer"},
{[]string{"adjust-order", "Nonexistent", "1"}, "Error"},
}

for _, tc := range errorTests {
output := executeCommand(tc.args)
if !strings.Contains(output, tc.expectedErr) {
t.Errorf("Expected error '%s', got '%s'", tc.expectedErr, output)
}
}
}
Loading
Loading