Skip to content

Commit 5e10870

Browse files
committed
add more unit tests
Signed-off-by: Emily McMullan <[email protected]>
1 parent 77698ef commit 5e10870

File tree

2 files changed

+614
-9
lines changed

2 files changed

+614
-9
lines changed

cmd/config/config_test.go

Lines changed: 323 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"bytes"
66
"compress/gzip"
77
"context"
8+
"encoding/base64"
89
"encoding/json"
910
"io"
1011
"net/http"
@@ -20,6 +21,31 @@ import (
2021
"gopkg.in/yaml.v2"
2122
)
2223

24+
// generateMockJWT creates a mock JWT token for testing purposes
25+
// The token has a valid structure with an expiration claim set to a future date
26+
func generateMockJWT(expirationTime time.Time) string {
27+
// JWT Header
28+
header := map[string]interface{}{
29+
"alg": "HS256",
30+
"typ": "JWT",
31+
}
32+
headerJSON, _ := json.Marshal(header)
33+
headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON)
34+
35+
// JWT Payload with expiration claim
36+
payload := map[string]interface{}{
37+
"exp": expirationTime.Unix(),
38+
}
39+
payloadJSON, _ := json.Marshal(payload)
40+
payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON)
41+
42+
// Mock signature (not cryptographically valid, but sufficient for testing)
43+
signature := "mock_signature_for_testing"
44+
signatureB64 := base64.RawURLEncoding.EncodeToString([]byte(signature))
45+
46+
return headerB64 + "." + payloadB64 + "." + signatureB64
47+
}
48+
2349
func TestNewConfigCmd(t *testing.T) {
2450
log := logr.Discard()
2551
cmd := NewConfigCmd(log)
@@ -391,7 +417,7 @@ func TestSyncCommand_getHubClient(t *testing.T) {
391417
}
392418

393419
// Create test auth with a valid JWT token
394-
validJWT := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE4OTM0NTYwMDB9.Hs_ZQhwq7Uy9E7VzTpSKNqvWUdKKYKxWJhUlNhqJGKE"
420+
validJWT := generateMockJWT(time.Now().Add(1 * time.Hour))
395421

396422
testAuth := LoginResponse{
397423
Host: "http://test-host",
@@ -524,11 +550,8 @@ func TestHubClient_doRequest(t *testing.T) {
524550
}
525551

526552
// Create test auth with a valid JWT token
527-
// This is a simple JWT token with exp claim set to far future (year 2030)
528-
// Header: {"alg":"HS256","typ":"JWT"}
529-
// Payload: {"exp":1893456000}
530-
// Signature: signed with secret "test"
531-
validJWT := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE4OTM0NTYwMDB9.Hs_ZQhwq7Uy9E7VzTpSKNqvWUdKKYKxWJhUlNhqJGKE"
553+
// Using generateMockJWT to create a properly structured JWT for testing
554+
validJWT := generateMockJWT(time.Now().Add(1 * time.Hour))
532555

533556
testAuth := LoginResponse{
534557
Host: "http://test-host",
@@ -813,7 +836,7 @@ func TestSyncCommand_getApplicationFromHub(t *testing.T) {
813836
}
814837

815838
// Create test auth with a valid JWT token
816-
validJWT := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE4OTM0NTYwMDB9.Hs_ZQhwq7Uy9E7VzTpSKNqvWUdKKYKxWJhUlNhqJGKE"
839+
validJWT := generateMockJWT(time.Now().Add(1 * time.Hour))
817840

818841
testAuth := LoginResponse{
819842
Host: "http://test-host",
@@ -952,7 +975,7 @@ func TestSyncCommand_getProfilesFromHubApplication(t *testing.T) {
952975
}
953976

954977
// Create test auth with a valid JWT token
955-
validJWT := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE4OTM0NTYwMDB9.Hs_ZQhwq7Uy9E7VzTpSKNqvWUdKKYKxWJhUlNhqJGKE"
978+
validJWT := generateMockJWT(time.Now().Add(1 * time.Hour))
956979

957980
testAuth := LoginResponse{
958981
Host: "http://test-host",
@@ -1396,7 +1419,7 @@ func TestSyncCommand_downloadProfileBundle(t *testing.T) {
13961419
}
13971420

13981421
// Create test auth with a valid JWT token
1399-
validJWT := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE4OTM0NTYwMDB9.Hs_ZQhwq7Uy9E7VzTpSKNqvWUdKKYKxWJhUlNhqJGKE"
1422+
validJWT := generateMockJWT(time.Now().Add(1 * time.Hour))
14001423

14011424
testAuth := LoginResponse{
14021425
Host: "http://test-host",
@@ -1538,3 +1561,294 @@ func TestConfigCommandIntegration(t *testing.T) {
15381561
}
15391562
})
15401563
}
1564+
1565+
func TestSyncCommand_setDefaultApplicationPath(t *testing.T) {
1566+
syncCmd := &syncCommand{}
1567+
1568+
path, err := syncCmd.setDefaultApplicationPath()
1569+
1570+
if err != nil {
1571+
t.Errorf("setDefaultApplicationPath() unexpected error = %v", err)
1572+
}
1573+
1574+
if path == "" {
1575+
t.Error("setDefaultApplicationPath() returned empty path")
1576+
}
1577+
1578+
// Verify it returns a valid directory path
1579+
if _, err := os.Stat(path); os.IsNotExist(err) {
1580+
t.Errorf("setDefaultApplicationPath() returned non-existent path: %s", path)
1581+
}
1582+
}
1583+
1584+
func TestSyncCommand_checkAuthentication(t *testing.T) {
1585+
// Create temporary auth file for testing
1586+
homeDir, err := os.UserHomeDir()
1587+
if err != nil {
1588+
t.Fatalf("Failed to get home directory: %v", err)
1589+
}
1590+
1591+
kantreDir := filepath.Join(homeDir, ".kantra")
1592+
authFile := filepath.Join(kantreDir, "auth.json")
1593+
1594+
// Backup existing auth file if it exists
1595+
var backupData []byte
1596+
var hasBackup bool
1597+
if data, err := os.ReadFile(authFile); err == nil {
1598+
backupData = data
1599+
hasBackup = true
1600+
}
1601+
1602+
tests := []struct {
1603+
name string
1604+
setupAuth func() error
1605+
wantErr bool
1606+
errMsg string
1607+
}{
1608+
{
1609+
name: "valid authentication",
1610+
setupAuth: func() error {
1611+
validJWT := generateMockJWT(time.Now().Add(1 * time.Hour))
1612+
testAuth := LoginResponse{
1613+
Host: "http://test-host",
1614+
Token: validJWT,
1615+
RefreshToken: "test-refresh-token",
1616+
}
1617+
os.MkdirAll(kantreDir, 0755)
1618+
authData, _ := json.Marshal(testAuth)
1619+
return os.WriteFile(authFile, authData, 0600)
1620+
},
1621+
wantErr: false,
1622+
},
1623+
{
1624+
name: "missing token should fail",
1625+
setupAuth: func() error {
1626+
testAuth := LoginResponse{
1627+
Host: "http://test-host",
1628+
Token: "",
1629+
RefreshToken: "test-refresh-token",
1630+
}
1631+
os.MkdirAll(kantreDir, 0755)
1632+
authData, _ := json.Marshal(testAuth)
1633+
return os.WriteFile(authFile, authData, 0600)
1634+
},
1635+
wantErr: true,
1636+
errMsg: "stored authentication is invalid",
1637+
},
1638+
{
1639+
name: "no auth file should fail",
1640+
setupAuth: func() error {
1641+
os.Remove(authFile)
1642+
return nil
1643+
},
1644+
wantErr: true,
1645+
errMsg: "no stored authentication found",
1646+
},
1647+
}
1648+
1649+
// Cleanup function
1650+
defer func() {
1651+
if hasBackup {
1652+
os.WriteFile(authFile, backupData, 0600)
1653+
} else {
1654+
os.Remove(authFile)
1655+
}
1656+
}()
1657+
1658+
for _, tt := range tests {
1659+
t.Run(tt.name, func(t *testing.T) {
1660+
err := tt.setupAuth()
1661+
if err != nil {
1662+
t.Fatalf("Failed to setup auth: %v", err)
1663+
}
1664+
1665+
syncCmd := &syncCommand{}
1666+
err = syncCmd.checkAuthentication()
1667+
1668+
if tt.wantErr {
1669+
if err == nil {
1670+
t.Errorf("checkAuthentication() expected error but got none")
1671+
} else if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
1672+
t.Errorf("checkAuthentication() error = %v, expected to contain %v", err, tt.errMsg)
1673+
}
1674+
} else {
1675+
if err != nil {
1676+
t.Errorf("checkAuthentication() unexpected error = %v", err)
1677+
}
1678+
}
1679+
})
1680+
}
1681+
}
1682+
1683+
func TestHubClient_doRequestWithRetry(t *testing.T) {
1684+
log := logr.Discard()
1685+
1686+
// Create temporary auth file for testing
1687+
homeDir, err := os.UserHomeDir()
1688+
if err != nil {
1689+
t.Fatalf("Failed to get home directory: %v", err)
1690+
}
1691+
1692+
kantreDir := filepath.Join(homeDir, ".kantra")
1693+
authFile := filepath.Join(kantreDir, "auth.json")
1694+
1695+
// Backup existing auth file if it exists
1696+
var backupData []byte
1697+
var hasBackup bool
1698+
if data, err := os.ReadFile(authFile); err == nil {
1699+
backupData = data
1700+
hasBackup = true
1701+
}
1702+
1703+
// Create test auth with a valid JWT token
1704+
validJWT := generateMockJWT(time.Now().Add(1 * time.Hour))
1705+
testAuth := LoginResponse{
1706+
Host: "http://test-host",
1707+
Token: validJWT,
1708+
RefreshToken: "test-refresh-token",
1709+
}
1710+
1711+
// Ensure directory exists
1712+
os.MkdirAll(kantreDir, 0755)
1713+
1714+
// Write test auth
1715+
authData, _ := json.Marshal(testAuth)
1716+
os.WriteFile(authFile, authData, 0600)
1717+
1718+
// Cleanup function
1719+
defer func() {
1720+
if hasBackup {
1721+
os.WriteFile(authFile, backupData, 0600)
1722+
} else {
1723+
os.Remove(authFile)
1724+
}
1725+
}()
1726+
1727+
tests := []struct {
1728+
name string
1729+
serverResponse func(w http.ResponseWriter, r *http.Request)
1730+
isRetry bool
1731+
wantErr bool
1732+
errMsg string
1733+
}{
1734+
{
1735+
name: "successful request with retry flag",
1736+
serverResponse: func(w http.ResponseWriter, r *http.Request) {
1737+
w.Header().Set("Content-Type", "application/json")
1738+
w.WriteHeader(http.StatusOK)
1739+
w.Write([]byte(`{"status": "ok"}`))
1740+
},
1741+
isRetry: true,
1742+
wantErr: false,
1743+
},
1744+
{
1745+
name: "unauthorized response triggers retry",
1746+
serverResponse: func(w http.ResponseWriter, r *http.Request) {
1747+
w.WriteHeader(http.StatusUnauthorized)
1748+
w.Write([]byte("Unauthorized"))
1749+
},
1750+
isRetry: false,
1751+
wantErr: true, // Will fail because refresh token logic will fail in test
1752+
},
1753+
}
1754+
1755+
for _, tt := range tests {
1756+
t.Run(tt.name, func(t *testing.T) {
1757+
server := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
1758+
defer server.Close()
1759+
1760+
client := &hubClient{
1761+
client: &http.Client{Timeout: 5 * time.Second},
1762+
host: server.URL,
1763+
}
1764+
1765+
resp, err := client.doRequestWithRetry("/test", "application/json", log, tt.isRetry)
1766+
1767+
if tt.wantErr {
1768+
if err == nil {
1769+
t.Errorf("doRequestWithRetry() expected error but got none")
1770+
} else if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
1771+
t.Errorf("doRequestWithRetry() error = %v, expected to contain %v", err, tt.errMsg)
1772+
}
1773+
} else {
1774+
if err != nil {
1775+
t.Errorf("doRequestWithRetry() unexpected error = %v", err)
1776+
}
1777+
if resp != nil {
1778+
resp.Body.Close()
1779+
}
1780+
}
1781+
})
1782+
}
1783+
}
1784+
1785+
func TestHubClient_refreshStoredToken(t *testing.T) {
1786+
log := logr.Discard()
1787+
1788+
tests := []struct {
1789+
name string
1790+
serverResponse func(w http.ResponseWriter, r *http.Request)
1791+
wantErr bool
1792+
errMsg string
1793+
}{
1794+
{
1795+
name: "successful token refresh",
1796+
serverResponse: func(w http.ResponseWriter, r *http.Request) {
1797+
if r.URL.Path == "/auth/refresh" {
1798+
w.Header().Set("Content-Type", "application/json")
1799+
w.WriteHeader(http.StatusOK)
1800+
response := LoginResponse{
1801+
Host: "http://test-host",
1802+
Token: "new-token",
1803+
RefreshToken: "new-refresh-token",
1804+
}
1805+
json.NewEncoder(w).Encode(response)
1806+
} else {
1807+
w.WriteHeader(http.StatusNotFound)
1808+
}
1809+
},
1810+
wantErr: false,
1811+
},
1812+
{
1813+
name: "refresh endpoint error",
1814+
serverResponse: func(w http.ResponseWriter, r *http.Request) {
1815+
w.WriteHeader(http.StatusUnauthorized)
1816+
w.Write([]byte("Refresh failed"))
1817+
},
1818+
wantErr: true,
1819+
errMsg: "token refresh failed with status 401",
1820+
},
1821+
}
1822+
1823+
for _, tt := range tests {
1824+
t.Run(tt.name, func(t *testing.T) {
1825+
server := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
1826+
defer server.Close()
1827+
1828+
client := &hubClient{
1829+
client: &http.Client{Timeout: 5 * time.Second},
1830+
host: server.URL,
1831+
}
1832+
1833+
storedAuth := &LoginResponse{
1834+
Host: server.URL,
1835+
Token: "old-token",
1836+
RefreshToken: "old-refresh-token",
1837+
}
1838+
1839+
err := client.refreshStoredToken(storedAuth, log)
1840+
1841+
if tt.wantErr {
1842+
if err == nil {
1843+
t.Errorf("refreshStoredToken() expected error but got none")
1844+
} else if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
1845+
t.Errorf("refreshStoredToken() error = %v, expected to contain %v", err, tt.errMsg)
1846+
}
1847+
} else {
1848+
if err != nil {
1849+
t.Errorf("refreshStoredToken() unexpected error = %v", err)
1850+
}
1851+
}
1852+
})
1853+
}
1854+
}

0 commit comments

Comments
 (0)