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+
2349func 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