Skip to content

Commit e7bd1dc

Browse files
committed
test: improved test coverage to above 80%
1 parent 558e14f commit e7bd1dc

4 files changed

Lines changed: 259 additions & 7 deletions

File tree

internal/cli/root.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,17 +172,17 @@ func addVersionCheckCommand(rootCmd *cobra.Command, currentVersion string) {
172172
Use: "version",
173173
Short: "Print the version number and check for updates",
174174
RunE: func(cmd *cobra.Command, _ []string) error {
175-
fmt.Printf("toolkit version: %s\n", currentVersion)
175+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "toolkit version: %s\n", currentVersion)
176176
check, _ := cmd.Flags().GetBool("check")
177177
if check {
178178
latest, err := fetchLatestRelease()
179179
if err != nil {
180180
return fmt.Errorf("failed to check latest version: %w", err)
181181
}
182182
if latest == currentVersion {
183-
fmt.Println("You are running the latest version.")
183+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "You are running the latest version.")
184184
} else {
185-
fmt.Printf("A newer version is available: %s\n", latest)
185+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "A newer version is available: %s\n", latest)
186186
}
187187
}
188188
return nil

internal/ui/tui/actions/actions.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/jingle2008/toolkit/pkg/models"
99
)
1010

11+
var clipboardWriteAll = clipboard.WriteAll
12+
1113
// CopyItemName copies the name or ID of an item to the clipboard.
1214
func CopyItemName(item any, env *models.Environment, logger logging.Logger) {
1315
if item == nil {
@@ -17,11 +19,11 @@ func CopyItemName(item any, env *models.Environment, logger logging.Logger) {
1719

1820
if dac, ok := item.(*models.DedicatedAICluster); ok {
1921
id := dac.GetID(env.Realm, env.Region)
20-
if err := clipboard.WriteAll(id); err != nil {
22+
if err := clipboardWriteAll(id); err != nil {
2123
logger.Errorw("failed to copy id to clipboard", "error", err)
2224
}
2325
} else if to, ok := item.(models.NamedItem); ok {
24-
if err := clipboard.WriteAll(to.GetName()); err != nil {
26+
if err := clipboardWriteAll(to.GetName()); err != nil {
2527
logger.Errorw("failed to copy name to clipboard", "error", err)
2628
}
2729
} else {
@@ -38,11 +40,11 @@ func CopyTenantID(item any, env *models.Environment, logger logging.Logger) {
3840

3941
if dac, ok := item.(*models.DedicatedAICluster); ok {
4042
tenantID := dac.GetTenantID(env.Realm)
41-
if err := clipboard.WriteAll(tenantID); err != nil {
43+
if err := clipboardWriteAll(tenantID); err != nil {
4244
logger.Errorw("failed to copy tenantID to clipboard", "error", err)
4345
}
4446
} else if to, ok := item.(models.TenancyOverride); ok {
45-
if err := clipboard.WriteAll(to.GetTenantID()); err != nil {
47+
if err := clipboardWriteAll(to.GetTenantID()); err != nil {
4648
logger.Errorw("failed to copy tenantID to clipboard", "error", err)
4749
}
4850
} else {
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package actions
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/jingle2008/toolkit/pkg/infra/logging"
8+
"github.com/jingle2008/toolkit/pkg/models"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
var (
13+
clipboardValue string
14+
clipboardErr error
15+
)
16+
17+
// monkey-patch clipboard.WriteAll for tests
18+
func init() {
19+
clipboardWriteAll = func(s string) error {
20+
clipboardValue = s
21+
return clipboardErr
22+
}
23+
}
24+
25+
// fakeLogger records error messages for assertions
26+
type fakeLogger struct {
27+
msgs []string
28+
}
29+
30+
func (f *fakeLogger) Errorw(msg string, kv ...any) {
31+
f.msgs = append(f.msgs, msg)
32+
}
33+
func (f *fakeLogger) Debugw(string, ...any) {}
34+
func (f *fakeLogger) Infow(string, ...any) {}
35+
func (f *fakeLogger) Sync() error { return nil }
36+
func (f *fakeLogger) WithFields(...any) logging.Logger { return f }
37+
func (f *fakeLogger) DebugEnabled() bool { return false }
38+
39+
func TestCopyItemName_Nil(t *testing.T) {
40+
t.Parallel()
41+
logger := &fakeLogger{}
42+
CopyItemName(nil, &models.Environment{}, logger)
43+
assert.Contains(t, logger.msgs[0], "no item selected")
44+
}
45+
46+
type fakeNamed struct{ name string }
47+
48+
func (f fakeNamed) GetName() string { return f.name }
49+
50+
func TestCopyItemName_NamedItem(t *testing.T) {
51+
t.Parallel()
52+
logger := &fakeLogger{}
53+
clipboardValue = ""
54+
clipboardErr = nil
55+
CopyItemName(fakeNamed{"foo"}, &models.Environment{}, logger)
56+
assert.Equal(t, "foo", clipboardValue)
57+
}
58+
59+
func TestCopyItemName_DedicatedAICluster(t *testing.T) {
60+
t.Parallel()
61+
logger := &fakeLogger{}
62+
clipboardValue = ""
63+
clipboardErr = nil
64+
dac := &models.DedicatedAICluster{}
65+
env := &models.Environment{Realm: "realm", Region: "region"}
66+
CopyItemName(dac, env, logger)
67+
}
68+
69+
func TestCopyItemName_Unsupported(t *testing.T) {
70+
t.Parallel()
71+
logger := &fakeLogger{}
72+
CopyItemName(123, &models.Environment{}, logger)
73+
assert.Contains(t, logger.msgs[len(logger.msgs)-1], "unsupported item type")
74+
}
75+
76+
type fakeTenancyOverride struct{ tenantID string }
77+
78+
func (f fakeTenancyOverride) GetTenantID() string { return f.tenantID }
79+
80+
func TestCopyTenantID_TenancyOverride(t *testing.T) {
81+
t.Parallel()
82+
logger := &fakeLogger{}
83+
clipboardValue = ""
84+
clipboardErr = nil
85+
to := fakeTenancyOverride{"tid"}
86+
CopyTenantID(to, &models.Environment{}, logger)
87+
assert.Equal(t, "tid", clipboardValue)
88+
}
89+
90+
func TestCopyTenantID_DedicatedAICluster(t *testing.T) {
91+
t.Parallel()
92+
logger := &fakeLogger{}
93+
clipboardValue = ""
94+
clipboardErr = nil
95+
dac := &models.DedicatedAICluster{}
96+
env := &models.Environment{Realm: "realm"}
97+
CopyTenantID(dac, env, logger)
98+
}
99+
100+
func TestCopyTenantID_Nil(t *testing.T) {
101+
t.Parallel()
102+
logger := &fakeLogger{}
103+
CopyTenantID(nil, &models.Environment{}, logger)
104+
assert.Contains(t, logger.msgs[0], "no item selected")
105+
}
106+
107+
func TestCopyTenantID_Unsupported(t *testing.T) {
108+
t.Parallel()
109+
logger := &fakeLogger{}
110+
CopyTenantID(123, &models.Environment{}, logger)
111+
assert.Contains(t, logger.msgs[len(logger.msgs)-1], "unsupported item type")
112+
}
113+
114+
func TestCopyItemName_ClipboardError(t *testing.T) {
115+
t.Parallel()
116+
logger := &fakeLogger{}
117+
clipboardErr = errors.New("fail")
118+
CopyItemName(fakeNamed{"foo"}, &models.Environment{}, logger)
119+
assert.Contains(t, logger.msgs[len(logger.msgs)-1], "failed to copy name")
120+
clipboardErr = nil
121+
}
122+
123+
func TestCopyTenantID_ClipboardError(t *testing.T) {
124+
t.Parallel()
125+
logger := &fakeLogger{}
126+
clipboardErr = errors.New("fail")
127+
to := fakeTenancyOverride{"tid"}
128+
CopyTenantID(to, &models.Environment{}, logger)
129+
assert.Contains(t, logger.msgs[len(logger.msgs)-1], "failed to copy tenantID")
130+
clipboardErr = nil
131+
}

internal/ui/tui/export_csv_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package tui
2+
3+
import (
4+
"encoding/csv"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/charmbracelet/bubbles/table"
10+
"github.com/jingle2008/toolkit/internal/domain"
11+
"github.com/jingle2008/toolkit/pkg/models"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestExportTableCSV_Success(t *testing.T) {
17+
t.Parallel()
18+
tmpDir := t.TempDir()
19+
outPath := filepath.Join(tmpDir, "out.csv")
20+
21+
// Build a minimal Model with headers and table rows
22+
headers := []header{
23+
{text: "Name"},
24+
{text: "Age"},
25+
}
26+
rows := []table.Row{
27+
{"Alice", "30"},
28+
{"Bob", "25"},
29+
}
30+
tbl := table.New()
31+
tbl.SetColumns([]table.Column{{Title: "Name"}, {Title: "Age"}})
32+
tbl.SetRows(rows)
33+
34+
m := &Model{
35+
headers: headers,
36+
table: &tbl,
37+
category: domain.Tenant,
38+
environment: models.Environment{Region: "us-ashburn-1", Realm: "oc1"},
39+
dataset: &models.Dataset{},
40+
loader: fakeLoader{dataset: &models.Dataset{}},
41+
logger: fakeLogger{},
42+
}
43+
44+
err := m.exportTableCSV(outPath)
45+
require.NoError(t, err)
46+
47+
// Read and check the CSV file
48+
// #nosec G304 -- test code, not user input
49+
f, err := os.Open(outPath)
50+
require.NoError(t, err)
51+
defer func() {
52+
_ = f.Close()
53+
}()
54+
r := csv.NewReader(f)
55+
records, err := r.ReadAll()
56+
require.NoError(t, err)
57+
require.Len(t, records, 3)
58+
assert.Equal(t, []string{"Name", "Age"}, records[0])
59+
assert.Equal(t, []string{"Alice", "30"}, records[1])
60+
assert.Equal(t, []string{"Bob", "25"}, records[2])
61+
}
62+
63+
func TestExportTableCSV_NilModelOrTable(t *testing.T) {
64+
t.Parallel()
65+
// Nil model
66+
var m *Model
67+
err := m.exportTableCSV("foo.csv")
68+
require.Error(t, err)
69+
assert.Contains(t, err.Error(), "no table data")
70+
71+
// Nil table
72+
m2 := &Model{}
73+
err2 := m2.exportTableCSV("foo.csv")
74+
require.Error(t, err2)
75+
assert.Contains(t, err2.Error(), "no table data")
76+
}
77+
78+
func TestExportTableCSV_CreateFileError(t *testing.T) {
79+
t.Parallel()
80+
// Use a path in a non-existent directory
81+
m := &Model{
82+
headers: []header{{text: "A"}},
83+
table: &table.Model{},
84+
category: domain.Tenant,
85+
environment: models.Environment{Region: "us-ashburn-1", Realm: "oc1"},
86+
dataset: &models.Dataset{},
87+
loader: fakeLoader{dataset: &models.Dataset{}},
88+
logger: fakeLogger{},
89+
}
90+
badPath := filepath.Join("no_such_dir", "out.csv")
91+
err := m.exportTableCSV(badPath)
92+
require.Error(t, err)
93+
}
94+
95+
func TestExportFilename(t *testing.T) {
96+
t.Parallel()
97+
m := &Model{
98+
environment: models.Environment{Region: "iad"},
99+
category: domain.Tenant,
100+
loader: fakeLoader{dataset: &models.Dataset{}},
101+
logger: fakeLogger{},
102+
}
103+
got := m.exportFilename()
104+
assert.Equal(t, "iad-Tenant.csv", got)
105+
}
106+
107+
func TestExportView_ContainsFilenameAndPrompt(t *testing.T) {
108+
t.Parallel()
109+
m := &Model{
110+
environment: models.Environment{Region: "phx"},
111+
category: domain.Tenant,
112+
loader: fakeLoader{dataset: &models.Dataset{}},
113+
logger: fakeLogger{},
114+
}
115+
setDefaults(m)
116+
view := m.exportView()
117+
assert.Contains(t, view, m.exportFilename())
118+
assert.Contains(t, view, "Pick an export path:")
119+
}

0 commit comments

Comments
 (0)