Implement git handlers integration test

This commit is contained in:
2024-11-29 23:14:36 +01:00
parent 6aa3fd6c65
commit 1ddf93a8be
6 changed files with 352 additions and 11 deletions

View File

@@ -0,0 +1,181 @@
//go:build integration
package handlers_test
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"testing"
"novamd/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGitHandlers_Integration(t *testing.T) {
h := setupTestHarness(t)
defer h.teardown(t)
t.Run("git operations", func(t *testing.T) {
// Setup: Create a workspace with Git enabled
workspace := &models.Workspace{
UserID: h.RegularUser.ID,
Name: "Git Test Workspace",
GitEnabled: true,
GitURL: "https://github.com/test/repo.git",
GitUser: "testuser",
GitToken: "testtoken",
GitAutoCommit: true,
GitCommitMsgTemplate: "Update: {{message}}",
}
rr := h.makeRequest(t, http.MethodPost, "/api/v1/workspaces", workspace, h.RegularToken, nil)
require.Equal(t, http.StatusOK, rr.Code)
err := json.NewDecoder(rr.Body).Decode(workspace)
require.NoError(t, err)
// Construct base URL for Git operations
baseURL := "/api/v1/workspaces/" + url.PathEscape(workspace.Name) + "/git"
t.Run("stage, commit and push", func(t *testing.T) {
h.MockGit.Reset()
t.Run("successful commit", func(t *testing.T) {
commitMsg := "Test commit message"
requestBody := map[string]string{
"message": commitMsg,
}
rr := h.makeRequest(t, http.MethodPost, baseURL+"/commit", requestBody, h.RegularToken, nil)
require.Equal(t, http.StatusOK, rr.Code)
var response map[string]string
err := json.NewDecoder(rr.Body).Decode(&response)
require.NoError(t, err)
assert.Contains(t, response["message"], "successfully")
// Verify mock was called correctly
assert.Equal(t, 1, h.MockGit.GetCommitCount(), "Commit should be called once")
assert.Equal(t, 1, h.MockGit.GetPushCount(), "Push should be called once")
assert.Equal(t, commitMsg, h.MockGit.GetLastCommitMessage(), "Commit message should match")
})
t.Run("empty commit message", func(t *testing.T) {
h.MockGit.Reset()
requestBody := map[string]string{
"message": "",
}
rr := h.makeRequest(t, http.MethodPost, baseURL+"/commit", requestBody, h.RegularToken, nil)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Equal(t, 0, h.MockGit.GetCommitCount(), "Commit should not be called")
})
t.Run("git error", func(t *testing.T) {
h.MockGit.Reset()
h.MockGit.SetError(fmt.Errorf("mock git error"))
requestBody := map[string]string{
"message": "Test message",
}
rr := h.makeRequest(t, http.MethodPost, baseURL+"/commit", requestBody, h.RegularToken, nil)
assert.Equal(t, http.StatusInternalServerError, rr.Code)
h.MockGit.SetError(nil) // Reset error state
})
})
t.Run("pull changes", func(t *testing.T) {
h.MockGit.Reset()
t.Run("successful pull", func(t *testing.T) {
rr := h.makeRequest(t, http.MethodPost, baseURL+"/pull", nil, h.RegularToken, nil)
require.Equal(t, http.StatusOK, rr.Code)
var response map[string]string
err := json.NewDecoder(rr.Body).Decode(&response)
require.NoError(t, err)
assert.Contains(t, response["message"], "Pulled changes")
assert.Equal(t, 1, h.MockGit.GetPullCount(), "Pull should be called once")
})
t.Run("git error", func(t *testing.T) {
h.MockGit.Reset()
h.MockGit.SetError(fmt.Errorf("mock git error"))
rr := h.makeRequest(t, http.MethodPost, baseURL+"/pull", nil, h.RegularToken, nil)
assert.Equal(t, http.StatusInternalServerError, rr.Code)
h.MockGit.SetError(nil) // Reset error state
})
})
t.Run("unauthorized access", func(t *testing.T) {
h.MockGit.Reset()
tests := []struct {
name string
method string
path string
body interface{}
}{
{
name: "commit without token",
method: http.MethodPost,
path: baseURL + "/commit",
body: map[string]string{"message": "test"},
},
{
name: "pull without token",
method: http.MethodPost,
path: baseURL + "/pull",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Test without token
rr := h.makeRequest(t, tc.method, tc.path, tc.body, "", nil)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
// Test with wrong user's token
rr = h.makeRequest(t, tc.method, tc.path, tc.body, h.AdminToken, nil)
assert.Equal(t, http.StatusNotFound, rr.Code)
})
}
})
t.Run("workspace without git", func(t *testing.T) {
h.MockGit.Reset()
// Create a workspace without Git enabled
nonGitWorkspace := &models.Workspace{
UserID: h.RegularUser.ID,
Name: "Non-Git Workspace",
}
rr := h.makeRequest(t, http.MethodPost, "/api/v1/workspaces", nonGitWorkspace, h.RegularToken, nil)
require.Equal(t, http.StatusOK, rr.Code)
err := json.NewDecoder(rr.Body).Decode(nonGitWorkspace)
require.NoError(t, err)
nonGitBaseURL := "/api/v1/workspaces/" + url.PathEscape(nonGitWorkspace.Name) + "/git"
// Try to commit
commitMsg := map[string]string{"message": "test"}
rr = h.makeRequest(t, http.MethodPost, nonGitBaseURL+"/commit", commitMsg, h.RegularToken, nil)
assert.Equal(t, http.StatusInternalServerError, rr.Code)
// Try to pull
rr = h.makeRequest(t, http.MethodPost, nonGitBaseURL+"/pull", nil, h.RegularToken, nil)
assert.Equal(t, http.StatusInternalServerError, rr.Code)
})
})
}

View File

@@ -17,6 +17,7 @@ import (
"novamd/internal/api" "novamd/internal/api"
"novamd/internal/auth" "novamd/internal/auth"
"novamd/internal/db" "novamd/internal/db"
"novamd/internal/git"
"novamd/internal/handlers" "novamd/internal/handlers"
"novamd/internal/models" "novamd/internal/models"
"novamd/internal/secrets" "novamd/internal/secrets"
@@ -36,6 +37,7 @@ type testHarness struct {
RegularUser *models.User RegularUser *models.User
RegularToken string RegularToken string
TempDirectory string TempDirectory string
MockGit *MockGitClient
} }
// setupTestHarness creates a new test environment // setupTestHarness creates a new test environment
@@ -63,8 +65,16 @@ func setupTestHarness(t *testing.T) *testHarness {
t.Fatalf("Failed to run migrations: %v", err) t.Fatalf("Failed to run migrations: %v", err)
} }
// Initialize storage // Create mock git client
storageSvc := storage.NewService(tempDir) mockGit := NewMockGitClient(false)
// Create storage with mock git client
storageOpts := storage.Options{
NewGitClient: func(url, user, token, path string) git.Client {
return mockGit
},
}
storageSvc := storage.NewServiceWithOptions(tempDir, storageOpts)
// Initialize JWT service // Initialize JWT service
jwtSvc, err := auth.NewJWTService(auth.JWTConfig{ jwtSvc, err := auth.NewJWTService(auth.JWTConfig{
@@ -100,6 +110,7 @@ func setupTestHarness(t *testing.T) *testHarness {
JWTManager: jwtSvc, JWTManager: jwtSvc,
SessionSvc: sessionSvc, SessionSvc: sessionSvc,
TempDirectory: tempDir, TempDirectory: tempDir,
MockGit: mockGit,
} }
// Create test users // Create test users

View File

@@ -0,0 +1,123 @@
//go:build integration
package handlers_test
import (
"fmt"
)
// MockGitClient implements the git.Client interface for testing
type MockGitClient struct {
initialized bool
cloned bool
lastCommitMsg string
error error
pullCount int
commitCount int
pushCount int
cloneCount int
ensureCount int
}
// NewMockGitClient creates a new mock git client
func NewMockGitClient(shouldError bool) *MockGitClient {
var err error
if shouldError {
err = fmt.Errorf("mock git error")
}
return &MockGitClient{
error: err,
}
}
// Clone implements git.Client
func (m *MockGitClient) Clone() error {
if m.error != nil {
return m.error
}
m.cloneCount++
m.cloned = true
return nil
}
// Pull implements git.Client
func (m *MockGitClient) Pull() error {
if m.error != nil {
return m.error
}
m.pullCount++
return nil
}
// Commit implements git.Client
func (m *MockGitClient) Commit(message string) error {
if m.error != nil {
return m.error
}
m.commitCount++
m.lastCommitMsg = message
return nil
}
// Push implements git.Client
func (m *MockGitClient) Push() error {
if m.error != nil {
return m.error
}
m.pushCount++
return nil
}
// EnsureRepo implements git.Client
func (m *MockGitClient) EnsureRepo() error {
if m.error != nil {
return m.error
}
m.ensureCount++
m.initialized = true
return nil
}
// Helper methods for tests
func (m *MockGitClient) GetCommitCount() int {
return m.commitCount
}
func (m *MockGitClient) GetPushCount() int {
return m.pushCount
}
func (m *MockGitClient) GetPullCount() int {
return m.pullCount
}
func (m *MockGitClient) GetLastCommitMessage() string {
return m.lastCommitMsg
}
func (m *MockGitClient) IsInitialized() bool {
return m.initialized
}
func (m *MockGitClient) IsCloned() bool {
return m.cloned
}
// Reset resets all counters and states
func (m *MockGitClient) Reset() {
m.initialized = false
m.cloned = false
m.lastCommitMsg = ""
m.pullCount = 0
m.commitCount = 0
m.pushCount = 0
m.cloneCount = 0
m.ensureCount = 0
}
// SetError sets the error state
func (m *MockGitClient) SetError(err error) {
m.error = err
}

View File

@@ -52,6 +52,19 @@ func (h *Handler) CreateWorkspace() http.HandlerFunc {
return return
} }
if workspace.GitEnabled {
if err := h.Storage.SetupGitRepo(
ctx.UserID,
workspace.ID,
workspace.GitURL,
workspace.GitUser,
workspace.GitToken,
); err != nil {
http.Error(w, "Failed to setup git repo: "+err.Error(), http.StatusInternalServerError)
return
}
}
respondJSON(w, workspace) respondJSON(w, workspace)
} }
} }

View File

@@ -31,13 +31,18 @@ func (w *Workspace) Validate() error {
// SetDefaultSettings sets the default settings for the workspace // SetDefaultSettings sets the default settings for the workspace
func (w *Workspace) SetDefaultSettings() { func (w *Workspace) SetDefaultSettings() {
if w.Theme == "" {
w.Theme = "light" w.Theme = "light"
w.AutoSave = false }
w.ShowHiddenFiles = false
w.GitEnabled = false w.AutoSave = w.AutoSave || false
w.GitURL = "" w.ShowHiddenFiles = w.ShowHiddenFiles || false
w.GitUser = "" w.GitEnabled = w.GitEnabled || false
w.GitToken = ""
w.GitAutoCommit = false w.GitAutoCommit = w.GitEnabled && (w.GitAutoCommit || false)
if w.GitCommitMsgTemplate == "" {
w.GitCommitMsgTemplate = "${action} ${filename}" w.GitCommitMsgTemplate = "${action} ${filename}"
} }
}

View File

@@ -35,6 +35,14 @@ func NewService(rootDir string) *Service {
// NewServiceWithOptions creates a new Storage instance with the given options and the given rootDir root directory. // NewServiceWithOptions creates a new Storage instance with the given options and the given rootDir root directory.
func NewServiceWithOptions(rootDir string, options Options) *Service { func NewServiceWithOptions(rootDir string, options Options) *Service {
if options.Fs == nil {
options.Fs = &osFS{}
}
if options.NewGitClient == nil {
options.NewGitClient = git.New
}
return &Service{ return &Service{
fs: options.Fs, fs: options.Fs,
newGitClient: options.NewGitClient, newGitClient: options.NewGitClient,