From 4dcbcbdf864164519e6c931c2ca4581f44d94563 Mon Sep 17 00:00:00 2001 From: zhoujinyu <2319109590@qq.com> Date: Tue, 23 Dec 2025 20:34:39 +0800 Subject: [PATCH 1/3] test:pkg/picod Signed-off-by: zhoujinyu <2319109590@qq.com> --- pkg/picod/picod_test.go | 144 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/pkg/picod/picod_test.go b/pkg/picod/picod_test.go index f399caa4..477b04eb 100644 --- a/pkg/picod/picod_test.go +++ b/pkg/picod/picod_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -454,3 +455,146 @@ func TestPicoD_SetWorkspace(t *testing.T) { // but we still resolve /var aliasing for the parent directory part. assert.Equal(t, resolve(absLinkPath), resolve(server.workspaceDir)) } + +// TestParseFileMode tests filesystem utility for file mode parsing +func TestParseFileMode(t *testing.T) { + tests := []struct { + name string + modeStr string + expected os.FileMode + desc string + }{ + { + name: "Valid octal mode", + modeStr: "0644", + expected: 0644, + desc: "Should parse valid octal mode", + }, + { + name: "Empty mode defaults to 0644", + modeStr: "", + expected: 0644, + desc: "Should default to 0644", + }, + { + name: "Invalid mode defaults to 0644", + modeStr: "invalid", + expected: 0644, + desc: "Should default on invalid input", + }, + { + name: "Mode exceeding max defaults to 0644", + modeStr: "10000", + expected: 0644, + desc: "Should default when exceeding 0777", + }, + { + name: "Valid executable mode", + modeStr: "0755", + expected: 0755, + desc: "Should parse executable mode", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := parseFileMode(tt.modeStr) + assert.Equal(t, tt.expected, result, tt.desc) + }) + } +} + +// TestLoadBootstrapKey tests auth helper error paths +func TestLoadBootstrapKey(t *testing.T) { + tests := []struct { + name string + keyData []byte + expectErr bool + desc string + }{ + { + name: "Empty key data", + keyData: []byte{}, + expectErr: true, + desc: "Should reject empty key", + }, + { + name: "Invalid PEM format", + keyData: []byte("not a pem"), + expectErr: true, + desc: "Should reject invalid PEM", + }, + { + name: "Valid RSA public key", + keyData: func() []byte { _, pub := generateRSAKeys(t); return []byte(pub) }(), + expectErr: false, + desc: "Should accept valid RSA key", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + am := NewAuthManager() + err := am.LoadBootstrapKey(tt.keyData) + if tt.expectErr { + assert.Error(t, err, tt.desc) + } else { + assert.NoError(t, err, tt.desc) + } + }) + } +} + +// TestExecuteHandler_ErrorPaths tests execution pipeline error paths +func TestExecuteHandler_ErrorPaths(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "picod_execute_test") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + _, bootstrapPubStr := generateRSAKeys(t) + server := NewServer(Config{ + BootstrapKey: []byte(bootstrapPubStr), + Workspace: tmpDir, + }) + + tests := []struct { + name string + request string + statusCode int + desc string + }{ + { + name: "Empty command", + request: `{"command": []}`, + statusCode: http.StatusBadRequest, + desc: "Should reject empty command", + }, + { + name: "Invalid JSON", + request: `{"command": invalid}`, + statusCode: http.StatusBadRequest, + desc: "Should reject invalid JSON", + }, + { + name: "Invalid timeout format", + request: `{"command": ["echo", "test"], "timeout": "invalid"}`, + statusCode: http.StatusBadRequest, + desc: "Should reject invalid timeout", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", "/api/execute", bytes.NewBufferString(tt.request)) + req.Header.Set("Content-Type", "application/json") + + ctx, _ := gin.CreateTestContext(w) + ctx.Request = req + + server.ExecuteHandler(ctx) + + assert.Equal(t, tt.statusCode, w.Code, tt.desc) + }) + } +} From 0b78ce522fe34d306c51ff5d4c069b21b606dd16 Mon Sep 17 00:00:00 2001 From: zhoujinyu <2319109590@qq.com> Date: Sat, 27 Dec 2025 19:50:39 +0800 Subject: [PATCH 2/3] add more necessary tests Signed-off-by: zhoujinyu <2319109590@qq.com> --- pkg/picod/picod_test.go | 340 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 339 insertions(+), 1 deletion(-) diff --git a/pkg/picod/picod_test.go b/pkg/picod/picod_test.go index 477b04eb..74ad1984 100644 --- a/pkg/picod/picod_test.go +++ b/pkg/picod/picod_test.go @@ -545,7 +545,7 @@ func TestLoadBootstrapKey(t *testing.T) { } } -// TestExecuteHandler_ErrorPaths tests execution pipeline error paths +// TestExecuteHandler_ErrorPaths tests execution pipeline error paths and normal command execution func TestExecuteHandler_ErrorPaths(t *testing.T) { tmpDir, err := os.MkdirTemp("", "picod_execute_test") require.NoError(t, err) @@ -562,6 +562,7 @@ func TestExecuteHandler_ErrorPaths(t *testing.T) { request string statusCode int desc string + validate func(*testing.T, *httptest.ResponseRecorder) }{ { name: "Empty command", @@ -581,6 +582,22 @@ func TestExecuteHandler_ErrorPaths(t *testing.T) { statusCode: http.StatusBadRequest, desc: "Should reject invalid timeout", }, + { + name: "Normal command execution", + request: `{"command": ["echo", "hello"]}`, + statusCode: http.StatusOK, + desc: "Should execute normal command successfully", + validate: func(t *testing.T, w *httptest.ResponseRecorder) { + var resp ExecuteResponse + err := json.Unmarshal(w.Body.Bytes(), &resp) + require.NoError(t, err) + assert.Equal(t, "hello\n", resp.Stdout) + assert.Equal(t, 0, resp.ExitCode) + assert.False(t, resp.StartTime.IsZero()) + assert.False(t, resp.EndTime.IsZero()) + assert.Greater(t, resp.Duration, 0.0) + }, + }, } for _, tt := range tests { @@ -595,6 +612,327 @@ func TestExecuteHandler_ErrorPaths(t *testing.T) { server.ExecuteHandler(ctx) assert.Equal(t, tt.statusCode, w.Code, tt.desc) + if tt.validate != nil { + tt.validate(t, w) + } + }) + } +} + +// TestLoadPublicKey tests loading public key from file +func TestLoadPublicKey(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "picod_load_public_key_test") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + tests := []struct { + name string + setup func(*testing.T, string) *AuthManager + expectErr bool + errContains string + validate func(*testing.T, *AuthManager) + }{ + { + name: "FileNotExists", + setup: func(t *testing.T, tmpDir string) *AuthManager { + am := NewAuthManager() + am.keyFile = filepath.Join(tmpDir, "test_key.pem") + return am + }, + expectErr: true, + errContains: "no public key file found", + }, + { + name: "InvalidPEM", + setup: func(t *testing.T, tmpDir string) *AuthManager { + am := NewAuthManager() + am.keyFile = filepath.Join(tmpDir, "test_key.pem") + err := os.WriteFile(am.keyFile, []byte("not a pem"), 0644) + require.NoError(t, err) + return am + }, + expectErr: true, + errContains: "failed to decode PEM block", + }, + { + name: "ValidKey", + setup: func(t *testing.T, tmpDir string) *AuthManager { + am := NewAuthManager() + am.keyFile = filepath.Join(tmpDir, "test_key.pem") + _, pubStr := generateRSAKeys(t) + err := os.WriteFile(am.keyFile, []byte(pubStr), 0644) + require.NoError(t, err) + return am + }, + expectErr: false, + validate: func(t *testing.T, am *AuthManager) { + assert.True(t, am.IsInitialized()) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + am := tt.setup(t, tmpDir) + err := am.LoadPublicKey() + if tt.expectErr { + assert.Error(t, err) + if tt.errContains != "" { + assert.Contains(t, err.Error(), tt.errContains) + } + } else { + assert.NoError(t, err) + if tt.validate != nil { + tt.validate(t, am) + } + } + }) + } +} + +// TestInitHandler_ErrorPaths tests InitHandler error paths +func TestInitHandler_ErrorPaths(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "picod_init_test") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + bootstrapPriv, bootstrapPubStr := generateRSAKeys(t) + + tests := []struct { + name string + setup func(*testing.T) (*AuthManager, *http.Request) + statusCode int + desc string + }{ + { + name: "MissingAuthHeader", + setup: func(t *testing.T) (*AuthManager, *http.Request) { + am := NewAuthManager() + am.keyFile = filepath.Join(tmpDir, "picod_public_key.pem") + err := am.LoadBootstrapKey([]byte(bootstrapPubStr)) + require.NoError(t, err) + req := httptest.NewRequest("POST", "/init", nil) + return am, req + }, + statusCode: http.StatusUnauthorized, + desc: "Should reject missing authorization header", + }, + { + name: "InvalidAuthHeaderFormat", + setup: func(t *testing.T) (*AuthManager, *http.Request) { + am := NewAuthManager() + am.keyFile = filepath.Join(tmpDir, "picod_public_key.pem") + err := am.LoadBootstrapKey([]byte(bootstrapPubStr)) + require.NoError(t, err) + req := httptest.NewRequest("POST", "/init", nil) + req.Header.Set("Authorization", "InvalidFormat token") + return am, req + }, + statusCode: http.StatusUnauthorized, + desc: "Should reject invalid authorization header format", + }, + { + name: "InvalidToken", + setup: func(t *testing.T) (*AuthManager, *http.Request) { + am := NewAuthManager() + am.keyFile = filepath.Join(tmpDir, "picod_public_key.pem") + err := am.LoadBootstrapKey([]byte(bootstrapPubStr)) + require.NoError(t, err) + req := httptest.NewRequest("POST", "/init", nil) + req.Header.Set("Authorization", "Bearer invalid-token") + return am, req + }, + statusCode: http.StatusUnauthorized, + desc: "Should reject invalid token", + }, + { + name: "MissingSessionPublicKey", + setup: func(t *testing.T) (*AuthManager, *http.Request) { + am := NewAuthManager() + am.keyFile = filepath.Join(tmpDir, "picod_public_key.pem") + err := am.LoadBootstrapKey([]byte(bootstrapPubStr)) + require.NoError(t, err) + claims := jwt.MapClaims{ + "iat": time.Now().Unix(), + "exp": time.Now().Add(time.Hour).Unix(), + } + token := createToken(t, bootstrapPriv, claims) + req := httptest.NewRequest("POST", "/init", nil) + req.Header.Set("Authorization", "Bearer "+token) + return am, req + }, + statusCode: http.StatusBadRequest, + desc: "Should reject missing session public key", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + am, req := tt.setup(t) + w := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(w) + ctx.Request = req + + am.InitHandler(ctx) + assert.Equal(t, tt.statusCode, w.Code, tt.desc) + }) + } +} + +// TestAuthMiddleware_ErrorPaths tests AuthMiddleware error paths +func TestAuthMiddleware_ErrorPaths(t *testing.T) { + tests := []struct { + name string + setup func(*testing.T) (*AuthManager, *http.Request) + statusCode int + desc string + }{ + { + name: "MissingAuthHeader", + setup: func(t *testing.T) (*AuthManager, *http.Request) { + tmpDir, err := os.MkdirTemp("", "picod_auth_test") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(tmpDir) }) + + am := NewAuthManager() + _, pubStr := generateRSAKeys(t) + am.keyFile = filepath.Join(tmpDir, "key.pem") + err = os.WriteFile(am.keyFile, []byte(pubStr), 0644) + require.NoError(t, err) + err = am.LoadPublicKey() + require.NoError(t, err) + + req := httptest.NewRequest("POST", "/api/execute", nil) + return am, req + }, + statusCode: http.StatusUnauthorized, + desc: "Should reject missing authorization header", + }, + { + name: "InvalidAuthHeaderFormat", + setup: func(t *testing.T) (*AuthManager, *http.Request) { + tmpDir, err := os.MkdirTemp("", "picod_auth_test") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(tmpDir) }) + + am := NewAuthManager() + _, pubStr := generateRSAKeys(t) + am.keyFile = filepath.Join(tmpDir, "key.pem") + err = os.WriteFile(am.keyFile, []byte(pubStr), 0644) + require.NoError(t, err) + err = am.LoadPublicKey() + require.NoError(t, err) + + req := httptest.NewRequest("POST", "/api/execute", nil) + req.Header.Set("Authorization", "InvalidFormat token") + return am, req + }, + statusCode: http.StatusUnauthorized, + desc: "Should reject invalid authorization header format", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + am, req := tt.setup(t) + w := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(w) + ctx.Request = req + + handler := am.AuthMiddleware() + handler(ctx) + + assert.True(t, ctx.IsAborted()) + assert.Equal(t, tt.statusCode, w.Code, tt.desc) + }) + } +} + +// TestDownloadFileHandler_ErrorPaths tests DownloadFileHandler error paths +func TestDownloadFileHandler_ErrorPaths(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "picod_download_test") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + _, bootstrapPubStr := generateRSAKeys(t) + server := NewServer(Config{ + BootstrapKey: []byte(bootstrapPubStr), + Workspace: tmpDir, + }) + + tests := []struct { + name string + path string + statusCode int + desc string + }{ + { + name: "FileNotFound", + path: "/nonexistent.txt", + statusCode: http.StatusNotFound, + desc: "Should return not found for non-existent file", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/api/files"+tt.path, nil) + ctx, _ := gin.CreateTestContext(w) + ctx.Request = req + ctx.Params = gin.Params{gin.Param{Key: "path", Value: tt.path}} + + server.DownloadFileHandler(ctx) + assert.Equal(t, tt.statusCode, w.Code, tt.desc) + }) + } +} + +// TestListFilesHandler_ErrorPaths tests ListFilesHandler error paths +func TestListFilesHandler_ErrorPaths(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "picod_list_test") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + _, bootstrapPubStr := generateRSAKeys(t) + server := NewServer(Config{ + BootstrapKey: []byte(bootstrapPubStr), + Workspace: tmpDir, + }) + + tests := []struct { + name string + path string + statusCode int + desc string + }{ + { + name: "MissingPathParameter", + path: "", + statusCode: http.StatusBadRequest, + desc: "Should reject missing path parameter", + }, + { + name: "DirectoryNotFound", + path: "nonexistent", + statusCode: http.StatusNotFound, + desc: "Should return not found for non-existent directory", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + url := "/api/files" + if tt.path != "" { + url += "?path=" + tt.path + } + req := httptest.NewRequest("GET", url, nil) + ctx, _ := gin.CreateTestContext(w) + ctx.Request = req + + server.ListFilesHandler(ctx) + assert.Equal(t, tt.statusCode, w.Code, tt.desc) }) } } From ad785cc889846817f7c27a8cdec775feb2c55c84 Mon Sep 17 00:00:00 2001 From: zhoujinyu <2319109590@qq.com> Date: Sat, 27 Dec 2025 19:55:33 +0800 Subject: [PATCH 3/3] lint Signed-off-by: zhoujinyu <2319109590@qq.com> --- pkg/picod/picod_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/picod/picod_test.go b/pkg/picod/picod_test.go index 74ad1984..6dd3b3ae 100644 --- a/pkg/picod/picod_test.go +++ b/pkg/picod/picod_test.go @@ -634,7 +634,7 @@ func TestLoadPublicKey(t *testing.T) { }{ { name: "FileNotExists", - setup: func(t *testing.T, tmpDir string) *AuthManager { + setup: func(_ *testing.T, tmpDir string) *AuthManager { am := NewAuthManager() am.keyFile = filepath.Join(tmpDir, "test_key.pem") return am @@ -647,7 +647,7 @@ func TestLoadPublicKey(t *testing.T) { setup: func(t *testing.T, tmpDir string) *AuthManager { am := NewAuthManager() am.keyFile = filepath.Join(tmpDir, "test_key.pem") - err := os.WriteFile(am.keyFile, []byte("not a pem"), 0644) + err := os.WriteFile(am.keyFile, []byte("not a pem"), 0600) require.NoError(t, err) return am }, @@ -660,7 +660,7 @@ func TestLoadPublicKey(t *testing.T) { am := NewAuthManager() am.keyFile = filepath.Join(tmpDir, "test_key.pem") _, pubStr := generateRSAKeys(t) - err := os.WriteFile(am.keyFile, []byte(pubStr), 0644) + err := os.WriteFile(am.keyFile, []byte(pubStr), 0600) require.NoError(t, err) return am }, @@ -797,7 +797,7 @@ func TestAuthMiddleware_ErrorPaths(t *testing.T) { am := NewAuthManager() _, pubStr := generateRSAKeys(t) am.keyFile = filepath.Join(tmpDir, "key.pem") - err = os.WriteFile(am.keyFile, []byte(pubStr), 0644) + err = os.WriteFile(am.keyFile, []byte(pubStr), 0600) require.NoError(t, err) err = am.LoadPublicKey() require.NoError(t, err) @@ -818,7 +818,7 @@ func TestAuthMiddleware_ErrorPaths(t *testing.T) { am := NewAuthManager() _, pubStr := generateRSAKeys(t) am.keyFile = filepath.Join(tmpDir, "key.pem") - err = os.WriteFile(am.keyFile, []byte(pubStr), 0644) + err = os.WriteFile(am.keyFile, []byte(pubStr), 0600) require.NoError(t, err) err = am.LoadPublicKey() require.NoError(t, err)