feat: golang linter
This commit is contained in:
@@ -33,6 +33,13 @@ jobs:
|
||||
- name: Run go vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.61.0
|
||||
args: ./...
|
||||
working-directory: apps/backend
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: |
|
||||
go test -v -race -coverprofile=coverage.out -covermode=atomic -json ./... > test-results.json 2>&1 || true
|
||||
@@ -85,4 +92,3 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./cmd/kandev
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Frontend Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
name: Run Frontend Lint, Tests, and Build
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: apps/pnpm-lock.yaml
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: '9'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Run lint
|
||||
run: pnpm --filter @kandev/web lint
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm --filter @kandev/web test
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter @kandev/web build
|
||||
@@ -7,6 +7,12 @@ repos:
|
||||
language: system
|
||||
pass_filenames: false
|
||||
|
||||
- id: go-lint
|
||||
name: Go Lint (backend)
|
||||
entry: make lint-backend
|
||||
language: system
|
||||
pass_filenames: false
|
||||
|
||||
- id: web-tests
|
||||
name: Web tests
|
||||
entry: make test-web
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
version: "2"
|
||||
# Default golangci-lint configuration (uses built-in defaults).
|
||||
@@ -68,7 +68,9 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "failed to initialize logger: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer log.Sync()
|
||||
defer func() {
|
||||
_ = log.Sync()
|
||||
}()
|
||||
|
||||
// Determine mode
|
||||
mode := detectMode(cfg.Mode)
|
||||
@@ -290,4 +292,3 @@ func collectAgentEnv() []string {
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ var (
|
||||
flagAgentRuntime = flag.String("agent-runtime", "", "Agent runtime mode: 'docker' or 'standalone'")
|
||||
flagPort = flag.Int("port", 0, "HTTP server port (default: 8080)")
|
||||
flagLogLevel = flag.String("log-level", "", "Log level: debug, info, warn, error")
|
||||
flagConfigPath = flag.String("config", "", "Path to config file")
|
||||
flagHelp = flag.Bool("help", false, "Show help message")
|
||||
flagVersion = flag.Bool("version", false, "Show version information")
|
||||
)
|
||||
@@ -127,7 +126,9 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer log.Sync()
|
||||
defer func() {
|
||||
_ = log.Sync()
|
||||
}()
|
||||
logger.SetDefault(log)
|
||||
|
||||
log.Info("Starting Kandev (unified mode)...")
|
||||
@@ -158,7 +159,9 @@ func main() {
|
||||
log.Warn("Failed to initialize Docker client - agent features will be disabled", zap.Error(err))
|
||||
dockerClient = nil
|
||||
} else {
|
||||
defer dockerClient.Close()
|
||||
defer func() {
|
||||
_ = dockerClient.Close()
|
||||
}()
|
||||
if err := dockerClient.Ping(ctx); err != nil {
|
||||
log.Warn("Docker daemon not available - agent features will be disabled", zap.Error(err))
|
||||
dockerClient = nil
|
||||
@@ -184,20 +187,26 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal("Failed to initialize SQLite database", zap.Error(err), zap.String("db_path", dbPath))
|
||||
}
|
||||
defer taskRepo.Close()
|
||||
defer func() {
|
||||
_ = taskRepo.Close()
|
||||
}()
|
||||
log.Info("SQLite database initialized", zap.String("db_path", dbPath))
|
||||
|
||||
agentSettingsRepo, err := settingsstore.NewSQLiteRepository(dbPath)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to initialize agent settings store", zap.Error(err), zap.String("db_path", dbPath))
|
||||
}
|
||||
defer agentSettingsRepo.Close()
|
||||
defer func() {
|
||||
_ = agentSettingsRepo.Close()
|
||||
}()
|
||||
|
||||
userRepo, err := userstore.NewSQLiteRepository(dbPath)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to initialize user store", zap.Error(err), zap.String("db_path", dbPath))
|
||||
}
|
||||
defer userRepo.Close()
|
||||
defer func() {
|
||||
_ = userRepo.Close()
|
||||
}()
|
||||
|
||||
discoveryRegistry, err := discovery.LoadRegistry()
|
||||
if err != nil {
|
||||
@@ -1041,7 +1050,7 @@ func (a *messageCreatorAdapter) CreateToolCallMessage(ctx context.Context, taskI
|
||||
}
|
||||
|
||||
// Add args if provided (contains kind, path, locations, raw_input)
|
||||
if args != nil && len(args) > 0 {
|
||||
if len(args) > 0 {
|
||||
metadata["args"] = args
|
||||
|
||||
// Extract kind as tool_name for icon selection in the frontend
|
||||
|
||||
@@ -51,7 +51,11 @@ func (c *Client) Initialize(ctx context.Context, clientName, clientVersion strin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
c.logger.Debug("failed to close initialize response body", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
var result InitializeResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
@@ -91,7 +95,11 @@ func (c *Client) NewSession(ctx context.Context, cwd string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
c.logger.Debug("failed to close session response body", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
var result NewSessionResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
@@ -137,7 +145,11 @@ func (c *Client) Prompt(ctx context.Context, text string) (*PromptResponse, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
c.logger.Debug("failed to close prompt response body", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
var result PromptResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
@@ -230,7 +242,9 @@ func (c *Client) StreamUpdates(ctx context.Context, handler func(SessionUpdate))
|
||||
c.mu.Lock()
|
||||
c.acpConn = nil
|
||||
c.mu.Unlock()
|
||||
conn.Close()
|
||||
if err := conn.Close(); err != nil {
|
||||
c.logger.Debug("failed to close updates websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
@@ -268,7 +282,9 @@ func (c *Client) CloseACPStream() {
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.acpConn != nil {
|
||||
c.acpConn.Close()
|
||||
if err := c.acpConn.Close(); err != nil {
|
||||
c.logger.Debug("failed to close ACP stream", zap.Error(err))
|
||||
}
|
||||
c.acpConn = nil
|
||||
}
|
||||
}
|
||||
@@ -294,7 +310,9 @@ func (c *Client) StreamPermissions(ctx context.Context, handler func(*Permission
|
||||
c.mu.Lock()
|
||||
c.permissionConn = nil
|
||||
c.mu.Unlock()
|
||||
conn.Close()
|
||||
if err := conn.Close(); err != nil {
|
||||
c.logger.Debug("failed to close permissions websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
@@ -327,7 +345,9 @@ func (c *Client) ClosePermissionStream() {
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.permissionConn != nil {
|
||||
c.permissionConn.Close()
|
||||
if err := c.permissionConn.Close(); err != nil {
|
||||
c.logger.Debug("failed to close permission stream", zap.Error(err))
|
||||
}
|
||||
c.permissionConn = nil
|
||||
}
|
||||
}
|
||||
@@ -355,7 +375,11 @@ func (c *Client) RespondToPermission(ctx context.Context, pendingID, optionID st
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
c.logger.Debug("failed to close permission response body", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
var result PermissionRespondResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
|
||||
@@ -62,7 +62,7 @@ func (c *Client) Health(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("health check failed: %d", resp.StatusCode)
|
||||
@@ -81,7 +81,7 @@ func (c *Client) GetStatus(ctx context.Context) (*StatusResponse, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
var status StatusResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
|
||||
@@ -101,7 +101,7 @@ func (c *Client) Start(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
var result struct {
|
||||
Success bool `json:"success"`
|
||||
@@ -127,7 +127,7 @@ func (c *Client) Stop(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
var result struct {
|
||||
Success bool `json:"success"`
|
||||
@@ -205,7 +205,9 @@ func (c *Client) StreamGitStatus(ctx context.Context, handler func(*GitStatusUpd
|
||||
c.mu.Lock()
|
||||
c.gitStatusConn = nil
|
||||
c.mu.Unlock()
|
||||
conn.Close()
|
||||
if err := conn.Close(); err != nil {
|
||||
c.logger.Debug("failed to close git status websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
@@ -238,7 +240,9 @@ func (c *Client) CloseGitStatusStream() {
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.gitStatusConn != nil {
|
||||
c.gitStatusConn.Close()
|
||||
if err := c.gitStatusConn.Close(); err != nil {
|
||||
c.logger.Debug("failed to close git status stream", zap.Error(err))
|
||||
}
|
||||
c.gitStatusConn = nil
|
||||
}
|
||||
}
|
||||
@@ -264,7 +268,9 @@ func (c *Client) StreamFileChanges(ctx context.Context, handler func(*FileChange
|
||||
c.mu.Lock()
|
||||
c.fileChangesConn = nil
|
||||
c.mu.Unlock()
|
||||
conn.Close()
|
||||
if err := conn.Close(); err != nil {
|
||||
c.logger.Debug("failed to close file changes websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
@@ -304,7 +310,11 @@ func (c *Client) RequestFileTree(ctx context.Context, path string, depth int) (*
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to request file tree: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
c.logger.Debug("failed to close file tree response body", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
var response FileTreeResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
@@ -331,7 +341,11 @@ func (c *Client) RequestFileContent(ctx context.Context, path string) (*FileCont
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to request file content: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
c.logger.Debug("failed to close file content response body", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
var response FileContentResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
@@ -351,7 +365,9 @@ func (c *Client) CloseFileChangesStream() {
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.fileChangesConn != nil {
|
||||
c.fileChangesConn.Close()
|
||||
if err := c.fileChangesConn.Close(); err != nil {
|
||||
c.logger.Debug("failed to close file changes stream", zap.Error(err))
|
||||
}
|
||||
c.fileChangesConn = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,9 @@ func (l *Launcher) Start(ctx context.Context) error {
|
||||
// Wait for health check to pass
|
||||
if err := l.waitForHealthy(ctx); err != nil {
|
||||
// Kill the process if health check fails
|
||||
l.cmd.Process.Kill()
|
||||
if killErr := l.cmd.Process.Kill(); killErr != nil {
|
||||
l.logger.Warn("failed to kill agentctl process after failed health check", zap.Error(killErr))
|
||||
}
|
||||
return fmt.Errorf("agentctl failed to become healthy: %w", err)
|
||||
}
|
||||
|
||||
@@ -230,7 +232,9 @@ func (l *Launcher) checkPortAvailable() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ln.Close()
|
||||
if err := ln.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -255,7 +259,9 @@ func (l *Launcher) waitForHealthy(ctx context.Context) error {
|
||||
|
||||
resp, err := client.Get(healthURL)
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
l.logger.Debug("failed to close health response body", zap.Error(closeErr))
|
||||
}
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -72,7 +72,9 @@ func (s *StandaloneCtl) Health(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("health check failed: %d", resp.StatusCode)
|
||||
@@ -97,13 +99,15 @@ func (s *StandaloneCtl) CreateInstance(ctx context.Context, req *CreateInstanceR
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create instance: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
|
||||
var errResp struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&errResp)
|
||||
if err := json.NewDecoder(resp.Body).Decode(&errResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode error response: %w", err)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to create instance: %s (status %d)", errResp.Error, resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -130,13 +134,17 @@ func (s *StandaloneCtl) DeleteInstance(ctx context.Context, instanceID string) e
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete instance: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
||||
var errResp struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&errResp)
|
||||
if err := json.NewDecoder(resp.Body).Decode(&errResp); err != nil {
|
||||
return fmt.Errorf("failed to decode error response: %w", err)
|
||||
}
|
||||
return fmt.Errorf("failed to delete instance: %s (status %d)", errResp.Error, resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -155,7 +163,7 @@ func (s *StandaloneCtl) GetInstance(ctx context.Context, instanceID string) (*In
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get instance: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, fmt.Errorf("instance %q not found", instanceID)
|
||||
@@ -182,7 +190,7 @@ func (s *StandaloneCtl) ListInstances(ctx context.Context) ([]*InstanceInfo, err
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list instances: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to list instances: status %d", resp.StatusCode)
|
||||
@@ -196,4 +204,3 @@ func (s *StandaloneCtl) ListInstances(ctx context.Context) ([]*InstanceInfo, err
|
||||
}
|
||||
return result.Instances, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,7 @@ func TestManager_GetCredential_FromEnv(t *testing.T) {
|
||||
// Set up test environment variable
|
||||
testKey := "TEST_CREDENTIAL_KEY_12345"
|
||||
testValue := "test-secret-value"
|
||||
os.Setenv(testKey, testValue)
|
||||
defer os.Unsetenv(testKey)
|
||||
t.Setenv(testKey, testValue)
|
||||
|
||||
log := newTestLogger()
|
||||
mgr := NewManager(log)
|
||||
@@ -74,8 +73,7 @@ func TestManager_GetCredential_FromEnv(t *testing.T) {
|
||||
func TestManager_GetCredential_Cached(t *testing.T) {
|
||||
testKey := "TEST_CACHED_KEY"
|
||||
testValue := "cached-value"
|
||||
os.Setenv(testKey, testValue)
|
||||
defer os.Unsetenv(testKey)
|
||||
t.Setenv(testKey, testValue)
|
||||
|
||||
log := newTestLogger()
|
||||
mgr := NewManager(log)
|
||||
@@ -90,7 +88,9 @@ func TestManager_GetCredential_Cached(t *testing.T) {
|
||||
}
|
||||
|
||||
// Remove from env - should still get cached value
|
||||
os.Unsetenv(testKey)
|
||||
if err := os.Unsetenv(testKey); err != nil {
|
||||
t.Fatalf("failed to unset env var: %v", err)
|
||||
}
|
||||
|
||||
cred2, err := mgr.GetCredential(ctx, testKey)
|
||||
if err != nil {
|
||||
@@ -117,12 +117,8 @@ func TestManager_GetCredential_NotFound(t *testing.T) {
|
||||
func TestManager_GetCredentials(t *testing.T) {
|
||||
testKey1 := "TEST_MULTI_KEY_1"
|
||||
testKey2 := "TEST_MULTI_KEY_2"
|
||||
os.Setenv(testKey1, "value1")
|
||||
os.Setenv(testKey2, "value2")
|
||||
defer func() {
|
||||
os.Unsetenv(testKey1)
|
||||
os.Unsetenv(testKey2)
|
||||
}()
|
||||
t.Setenv(testKey1, "value1")
|
||||
t.Setenv(testKey2, "value2")
|
||||
|
||||
log := newTestLogger()
|
||||
mgr := NewManager(log)
|
||||
@@ -147,8 +143,7 @@ func TestManager_GetCredentials(t *testing.T) {
|
||||
|
||||
func TestManager_GetCredentials_PartialFailure(t *testing.T) {
|
||||
testKey := "TEST_PARTIAL_KEY"
|
||||
os.Setenv(testKey, "value")
|
||||
defer os.Unsetenv(testKey)
|
||||
t.Setenv(testKey, "value")
|
||||
|
||||
log := newTestLogger()
|
||||
mgr := NewManager(log)
|
||||
@@ -169,8 +164,7 @@ func TestManager_GetCredentials_PartialFailure(t *testing.T) {
|
||||
func TestManager_BuildEnvVars(t *testing.T) {
|
||||
testKey := "TEST_BUILD_ENV_KEY"
|
||||
testValue := "build-env-value"
|
||||
os.Setenv(testKey, testValue)
|
||||
defer os.Unsetenv(testKey)
|
||||
t.Setenv(testKey, testValue)
|
||||
|
||||
log := newTestLogger()
|
||||
mgr := NewManager(log)
|
||||
@@ -219,8 +213,7 @@ func TestManager_BuildEnvVars_MissingRequired(t *testing.T) {
|
||||
|
||||
func TestManager_HasCredential(t *testing.T) {
|
||||
testKey := "TEST_HAS_CREDENTIAL"
|
||||
os.Setenv(testKey, "value")
|
||||
defer os.Unsetenv(testKey)
|
||||
t.Setenv(testKey, "value")
|
||||
|
||||
log := newTestLogger()
|
||||
mgr := NewManager(log)
|
||||
@@ -238,8 +231,7 @@ func TestManager_HasCredential(t *testing.T) {
|
||||
|
||||
func TestManager_ListAvailable(t *testing.T) {
|
||||
testKey := "TEST_LIST_AVAILABLE_API_KEY"
|
||||
os.Setenv(testKey, "value")
|
||||
defer os.Unsetenv(testKey)
|
||||
t.Setenv(testKey, "value")
|
||||
|
||||
log := newTestLogger()
|
||||
mgr := NewManager(log)
|
||||
@@ -263,8 +255,7 @@ func TestManager_ListAvailable(t *testing.T) {
|
||||
|
||||
func TestManager_ClearCache(t *testing.T) {
|
||||
testKey := "TEST_CLEAR_CACHE"
|
||||
os.Setenv(testKey, "original-value")
|
||||
defer os.Unsetenv(testKey)
|
||||
t.Setenv(testKey, "original-value")
|
||||
|
||||
log := newTestLogger()
|
||||
mgr := NewManager(log)
|
||||
@@ -279,7 +270,9 @@ func TestManager_ClearCache(t *testing.T) {
|
||||
}
|
||||
|
||||
// Update the env and clear cache
|
||||
os.Setenv(testKey, "new-value")
|
||||
if err := os.Setenv(testKey, "new-value"); err != nil {
|
||||
t.Fatalf("failed to update env var: %v", err)
|
||||
}
|
||||
mgr.ClearCache()
|
||||
|
||||
// Should get new value
|
||||
@@ -301,8 +294,7 @@ func TestEnvProvider_Name(t *testing.T) {
|
||||
func TestEnvProvider_GetCredential(t *testing.T) {
|
||||
testKey := "TEST_ENV_PROVIDER_KEY"
|
||||
testValue := "env-provider-value"
|
||||
os.Setenv(testKey, testValue)
|
||||
defer os.Unsetenv(testKey)
|
||||
t.Setenv(testKey, testValue)
|
||||
|
||||
provider := NewEnvProvider("")
|
||||
ctx := context.Background()
|
||||
@@ -323,8 +315,7 @@ func TestEnvProvider_GetCredential_WithPrefix(t *testing.T) {
|
||||
prefix := "KANDEV_"
|
||||
testKey := "MY_SECRET"
|
||||
testValue := "prefixed-value"
|
||||
os.Setenv(prefix+testKey, testValue)
|
||||
defer os.Unsetenv(prefix + testKey)
|
||||
t.Setenv(prefix+testKey, testValue)
|
||||
|
||||
provider := NewEnvProvider(prefix)
|
||||
ctx := context.Background()
|
||||
@@ -343,8 +334,7 @@ func TestEnvProvider_GetCredential_WithPrefix(t *testing.T) {
|
||||
|
||||
func TestEnvProvider_ListAvailable(t *testing.T) {
|
||||
testKey := "ANTHROPIC_API_KEY"
|
||||
os.Setenv(testKey, "test-value")
|
||||
defer os.Unsetenv(testKey)
|
||||
t.Setenv(testKey, "test-value")
|
||||
|
||||
provider := NewEnvProvider("")
|
||||
ctx := context.Background()
|
||||
@@ -412,7 +402,9 @@ func TestFileProvider_GetCredential_NotFound(t *testing.T) {
|
||||
|
||||
creds := map[string]string{"EXISTING": "value"}
|
||||
data, _ := json.Marshal(creds)
|
||||
os.WriteFile(configPath, data, 0644)
|
||||
if err := os.WriteFile(configPath, data, 0644); err != nil {
|
||||
t.Fatalf("failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
provider := NewFileProvider(configPath)
|
||||
ctx := context.Background()
|
||||
@@ -438,7 +430,9 @@ func TestFileProvider_InvalidJSON(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "invalid.json")
|
||||
|
||||
os.WriteFile(configPath, []byte("invalid json"), 0644)
|
||||
if err := os.WriteFile(configPath, []byte("invalid json"), 0644); err != nil {
|
||||
t.Fatalf("failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
provider := NewFileProvider(configPath)
|
||||
ctx := context.Background()
|
||||
@@ -458,7 +452,9 @@ func TestFileProvider_ListAvailable(t *testing.T) {
|
||||
"KEY_2": "value2",
|
||||
}
|
||||
data, _ := json.Marshal(creds)
|
||||
os.WriteFile(configPath, data, 0644)
|
||||
if err := os.WriteFile(configPath, data, 0644); err != nil {
|
||||
t.Fatalf("failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
provider := NewFileProvider(configPath)
|
||||
ctx := context.Background()
|
||||
@@ -480,7 +476,9 @@ func TestFileProvider_Reload(t *testing.T) {
|
||||
// Initial content
|
||||
creds := map[string]string{"KEY": "original"}
|
||||
data, _ := json.Marshal(creds)
|
||||
os.WriteFile(configPath, data, 0644)
|
||||
if err := os.WriteFile(configPath, data, 0644); err != nil {
|
||||
t.Fatalf("failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
provider := NewFileProvider(configPath)
|
||||
ctx := context.Background()
|
||||
@@ -494,7 +492,9 @@ func TestFileProvider_Reload(t *testing.T) {
|
||||
// Update file
|
||||
creds["KEY"] = "updated"
|
||||
data, _ = json.Marshal(creds)
|
||||
os.WriteFile(configPath, data, 0644)
|
||||
if err := os.WriteFile(configPath, data, 0644); err != nil {
|
||||
t.Fatalf("failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
// Reload
|
||||
err := provider.Reload()
|
||||
@@ -508,4 +508,3 @@ func TestFileProvider_Reload(t *testing.T) {
|
||||
t.Errorf("expected updated, got %q", cred.Value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,11 @@ func (c *Client) PullImage(ctx context.Context, imageName string) error {
|
||||
c.logger.Error("Failed to pull image", zap.String("image", imageName), zap.Error(err))
|
||||
return fmt.Errorf("failed to pull image %s: %w", imageName, err)
|
||||
}
|
||||
defer reader.Close()
|
||||
defer func() {
|
||||
if err := reader.Close(); err != nil {
|
||||
c.logger.Warn("Failed to close image pull reader", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Read the output to ensure the image is fully pulled
|
||||
_, err = io.Copy(io.Discard, reader)
|
||||
@@ -303,14 +307,8 @@ func (c *Client) GetContainerIP(ctx context.Context, containerID string) (string
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Try to get IP from default bridge network first
|
||||
if inspect.NetworkSettings != nil {
|
||||
// Check bridge network
|
||||
if inspect.NetworkSettings.IPAddress != "" {
|
||||
return inspect.NetworkSettings.IPAddress, nil
|
||||
}
|
||||
|
||||
// Check other networks
|
||||
// Check available networks for an IP address.
|
||||
for netName, netSettings := range inspect.NetworkSettings.Networks {
|
||||
if netSettings.IPAddress != "" {
|
||||
c.logger.Debug("Found container IP",
|
||||
@@ -538,7 +536,9 @@ func (c *Client) AttachContainer(ctx context.Context, containerID string) (*Atta
|
||||
|
||||
// Start goroutine to copy from pipe to container
|
||||
go func() {
|
||||
io.Copy(resp.Conn, stdinReader)
|
||||
if _, err := io.Copy(resp.Conn, stdinReader); err != nil {
|
||||
c.logger.Debug("failed to copy stdin to container", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Create a pipe for demultiplexed stdout
|
||||
@@ -547,7 +547,11 @@ func (c *Client) AttachContainer(ctx context.Context, containerID string) (*Atta
|
||||
|
||||
// Start goroutine to demultiplex stdout/stderr
|
||||
go func() {
|
||||
defer stdoutWriter.Close()
|
||||
defer func() {
|
||||
if err := stdoutWriter.Close(); err != nil {
|
||||
c.logger.Debug("failed to close stdout writer", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
c.demultiplexStream(resp.Reader, stdoutWriter)
|
||||
}()
|
||||
|
||||
@@ -594,7 +598,9 @@ func (c *Client) demultiplexStream(reader io.Reader, writer io.Writer) {
|
||||
// Write stdout (type 1) and stderr (type 2) to writer
|
||||
// We want both for ACP since errors should be visible
|
||||
if streamType == 1 || streamType == 2 {
|
||||
writer.Write(data)
|
||||
if _, err := writer.Write(data); err != nil {
|
||||
c.logger.Debug("failed to write demultiplexed data", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -603,10 +609,14 @@ func (c *Client) demultiplexStream(reader io.Reader, writer io.Writer) {
|
||||
// Close closes the attach result
|
||||
func (a *AttachResult) Close() error {
|
||||
if a.Stdin != nil {
|
||||
a.Stdin.Close()
|
||||
if err := a.Stdin.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if a.Conn != nil {
|
||||
a.Conn.Close()
|
||||
if err := a.Conn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -135,26 +135,6 @@ func newTestRegistry() *registry.Registry {
|
||||
return reg
|
||||
}
|
||||
|
||||
// managerWithMock creates a manager with a mock docker client
|
||||
// We need to use reflection or a wrapper to inject the mock
|
||||
type testManager struct {
|
||||
*Manager
|
||||
mockDocker *MockDockerClient
|
||||
}
|
||||
|
||||
func newTestManager(mockDocker *MockDockerClient, eventBus bus.EventBus) *testManager {
|
||||
log := newTestLogger()
|
||||
reg := newTestRegistry()
|
||||
|
||||
// Create a real manager first
|
||||
mgr := NewManager(nil, reg, eventBus, testAgentConfig(), log)
|
||||
|
||||
return &testManager{
|
||||
Manager: mgr,
|
||||
mockDocker: mockDocker,
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewManager(t *testing.T) {
|
||||
log := newTestLogger()
|
||||
reg := newTestRegistry()
|
||||
@@ -519,4 +499,3 @@ func TestManager_StartStop(t *testing.T) {
|
||||
t.Fatalf("unexpected error stopping manager: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,9 @@ func NewSQLiteRepository(dbPath string) (*SQLiteRepository, error) {
|
||||
|
||||
repo := &SQLiteRepository{db: db}
|
||||
if err := repo.initSchema(); err != nil {
|
||||
db.Close()
|
||||
if closeErr := db.Close(); closeErr != nil {
|
||||
return nil, fmt.Errorf("failed to close database after schema error: %w", closeErr)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to initialize schema: %w", err)
|
||||
}
|
||||
return repo, nil
|
||||
@@ -181,7 +183,9 @@ func (r *SQLiteRepository) ListAgents(ctx context.Context) ([]*models.Agent, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var result []*models.Agent
|
||||
for rows.Next() {
|
||||
@@ -258,7 +262,9 @@ func (r *SQLiteRepository) ListAgentProfiles(ctx context.Context, agentID string
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var result []*models.AgentProfile
|
||||
for rows.Next() {
|
||||
@@ -338,7 +344,9 @@ func columnExists(db *sql.DB, table, column string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
for rows.Next() {
|
||||
var cid int
|
||||
var name, ctype string
|
||||
|
||||
@@ -240,7 +240,9 @@ func (m *Manager) createWorktree(ctx context.Context, req CreateRequest) (*Workt
|
||||
if m.store != nil {
|
||||
if err := m.store.CreateWorktree(ctx, wt); err != nil {
|
||||
// Cleanup on failure
|
||||
m.removeWorktreeDir(ctx, worktreePath, req.RepositoryPath)
|
||||
if cleanupErr := m.removeWorktreeDir(ctx, worktreePath, req.RepositoryPath); cleanupErr != nil {
|
||||
m.logger.Warn("failed to cleanup worktree after persist failure", zap.Error(cleanupErr))
|
||||
}
|
||||
return nil, fmt.Errorf("failed to persist worktree: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -561,7 +563,9 @@ func (m *Manager) removeWorktreeDir(ctx context.Context, worktreePath, repoPath
|
||||
// Prune stale worktree entries
|
||||
cmd = exec.CommandContext(ctx, "git", "worktree", "prune")
|
||||
cmd.Dir = repoPath
|
||||
cmd.Run() // Ignore errors
|
||||
if err := cmd.Run(); err != nil {
|
||||
m.logger.Debug("git worktree prune failed", zap.Error(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -570,13 +574,17 @@ func (m *Manager) removeWorktreeDir(ctx context.Context, worktreePath, repoPath
|
||||
func (m *Manager) recreate(ctx context.Context, existing *Worktree, req CreateRequest) (*Worktree, error) {
|
||||
// Clean up existing directory if present
|
||||
if existing.Path != "" {
|
||||
os.RemoveAll(existing.Path)
|
||||
if err := os.RemoveAll(existing.Path); err != nil {
|
||||
m.logger.Debug("failed to remove existing worktree path", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from git worktree list
|
||||
cmd := exec.CommandContext(ctx, "git", "worktree", "prune")
|
||||
cmd.Dir = req.RepositoryPath
|
||||
cmd.Run() // Ignore errors
|
||||
if err := cmd.Run(); err != nil {
|
||||
m.logger.Debug("git worktree prune failed", zap.Error(err))
|
||||
}
|
||||
|
||||
// Get repository lock
|
||||
repoLock := m.getRepoLock(req.RepositoryPath)
|
||||
@@ -623,4 +631,4 @@ func (m *Manager) recreate(ctx context.Context, existing *Worktree, req CreateRe
|
||||
zap.String("path", worktreePath))
|
||||
|
||||
return existing, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ func (s *SQLiteStore) GetWorktreesByTaskID(ctx context.Context, taskID string) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
return s.scanWorktrees(rows)
|
||||
}
|
||||
@@ -166,7 +166,7 @@ func (s *SQLiteStore) GetWorktreesByRepositoryID(ctx context.Context, repoID str
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
return s.scanWorktrees(rows)
|
||||
}
|
||||
@@ -217,7 +217,7 @@ func (s *SQLiteStore) ListActiveWorktrees(ctx context.Context) ([]*Worktree, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
return s.scanWorktrees(rows)
|
||||
}
|
||||
|
||||
@@ -93,10 +93,10 @@ func (a *CodexAdapter) Initialize(ctx context.Context) error {
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Codex initialize handshake failed: %w", err)
|
||||
return fmt.Errorf("codex initialize handshake failed: %w", err)
|
||||
}
|
||||
if resp.Error != nil {
|
||||
return fmt.Errorf("Codex initialize error: %s", resp.Error.Message)
|
||||
return fmt.Errorf("codex initialize error: %s", resp.Error.Message)
|
||||
}
|
||||
|
||||
// Parse initialize result
|
||||
@@ -563,7 +563,9 @@ func (a *CodexAdapter) handleRequest(id interface{}, method string, params json.
|
||||
var p codex.CommandApprovalParams
|
||||
if err := json.Unmarshal(params, &p); err != nil {
|
||||
a.logger.Warn("failed to parse command approval request", zap.Error(err))
|
||||
a.client.SendResponse(id, nil, &codex.Error{Code: codex.InvalidParams, Message: "invalid params"})
|
||||
if err := a.client.SendResponse(id, nil, &codex.Error{Code: codex.InvalidParams, Message: "invalid params"}); err != nil {
|
||||
a.logger.Warn("failed to send invalid params response", zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
a.handleApprovalRequest(id, handler, threadID, p.ItemID, types.ActionTypeCommand, p.Command, map[string]interface{}{
|
||||
@@ -576,7 +578,9 @@ func (a *CodexAdapter) handleRequest(id interface{}, method string, params json.
|
||||
var p codex.FileChangeApprovalParams
|
||||
if err := json.Unmarshal(params, &p); err != nil {
|
||||
a.logger.Warn("failed to parse file change approval request", zap.Error(err))
|
||||
a.client.SendResponse(id, nil, &codex.Error{Code: codex.InvalidParams, Message: "invalid params"})
|
||||
if err := a.client.SendResponse(id, nil, &codex.Error{Code: codex.InvalidParams, Message: "invalid params"}); err != nil {
|
||||
a.logger.Warn("failed to send invalid params response", zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
a.handleApprovalRequest(id, handler, threadID, p.ItemID, types.ActionTypeFileWrite, p.Path, map[string]interface{}{
|
||||
@@ -587,7 +591,9 @@ func (a *CodexAdapter) handleRequest(id interface{}, method string, params json.
|
||||
|
||||
default:
|
||||
a.logger.Warn("unhandled request", zap.String("method", method))
|
||||
a.client.SendResponse(id, nil, &codex.Error{Code: codex.MethodNotFound, Message: "method not found"})
|
||||
if err := a.client.SendResponse(id, nil, &codex.Error{Code: codex.MethodNotFound, Message: "method not found"}); err != nil {
|
||||
a.logger.Warn("failed to send method not found response", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,9 +612,10 @@ func (a *CodexAdapter) handleApprovalRequest(
|
||||
options := make([]PermissionOption, len(optionStrings))
|
||||
for i, opt := range optionStrings {
|
||||
kind := "allow_once"
|
||||
if opt == "approveAlways" {
|
||||
switch opt {
|
||||
case "approveAlways":
|
||||
kind = "allow_always"
|
||||
} else if opt == "reject" {
|
||||
case "reject":
|
||||
kind = "reject_once"
|
||||
}
|
||||
options[i] = PermissionOption{
|
||||
@@ -638,10 +645,12 @@ func (a *CodexAdapter) handleApprovalRequest(
|
||||
if handler == nil {
|
||||
// Auto-approve if no handler
|
||||
a.logger.Info("auto-approving request (no handler)", zap.String("item_id", itemID))
|
||||
a.client.SendResponse(id, &codex.ApprovalResponse{
|
||||
if err := a.client.SendResponse(id, &codex.ApprovalResponse{
|
||||
ItemID: itemID,
|
||||
Decision: "approve",
|
||||
}, nil)
|
||||
}, nil); err != nil {
|
||||
a.logger.Warn("failed to send approval response", zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -650,10 +659,12 @@ func (a *CodexAdapter) handleApprovalRequest(
|
||||
resp, err := handler(ctx, req)
|
||||
if err != nil {
|
||||
a.logger.Error("permission handler error", zap.Error(err))
|
||||
a.client.SendResponse(id, &codex.ApprovalResponse{
|
||||
if err := a.client.SendResponse(id, &codex.ApprovalResponse{
|
||||
ItemID: itemID,
|
||||
Decision: "reject",
|
||||
}, nil)
|
||||
}, nil); err != nil {
|
||||
a.logger.Warn("failed to send reject response", zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -664,12 +675,13 @@ func (a *CodexAdapter) handleApprovalRequest(
|
||||
decision = resp.OptionID
|
||||
}
|
||||
|
||||
a.client.SendResponse(id, &codex.ApprovalResponse{
|
||||
if err := a.client.SendResponse(id, &codex.ApprovalResponse{
|
||||
ItemID: itemID,
|
||||
Decision: decision,
|
||||
}, nil)
|
||||
}, nil); err != nil {
|
||||
a.logger.Warn("failed to send approval response", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// Verify interface implementation
|
||||
var _ AgentAdapter = (*CodexAdapter)(nil)
|
||||
|
||||
|
||||
@@ -196,7 +196,11 @@ func (s *Server) handleACPStreamWS(c *gin.Context) {
|
||||
s.logger.Error("WebSocket upgrade failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
s.logger.Debug("failed to close ACP stream websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
s.logger.Info("ACP stream WebSocket connected")
|
||||
|
||||
@@ -204,22 +208,15 @@ func (s *Server) handleACPStreamWS(c *gin.Context) {
|
||||
updatesCh := s.procMgr.GetUpdates()
|
||||
|
||||
// Stream session notifications to WebSocket
|
||||
for {
|
||||
select {
|
||||
case notification, ok := <-updatesCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for notification := range updatesCh {
|
||||
data, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to marshal notification", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to marshal notification", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
return
|
||||
}
|
||||
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,7 +315,11 @@ func (s *Server) handlePermissionStreamWS(c *gin.Context) {
|
||||
s.logger.Error("WebSocket upgrade failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
s.logger.Debug("failed to close permission websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
s.logger.Info("Permission stream WebSocket connected")
|
||||
|
||||
@@ -326,23 +327,16 @@ func (s *Server) handlePermissionStreamWS(c *gin.Context) {
|
||||
permissionCh := s.procMgr.GetPermissionRequests()
|
||||
|
||||
// Stream permission notifications to WebSocket
|
||||
for {
|
||||
select {
|
||||
case notification, ok := <-permissionCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for notification := range permissionCh {
|
||||
data, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to marshal permission notification", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to marshal permission notification", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
s.logger.Debug("WebSocket write error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
s.logger.Debug("WebSocket write error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,28 +400,25 @@ func (m *ControlServer) handleACPStreamWS(c *gin.Context) {
|
||||
m.logger.Error("WebSocket upgrade failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
m.logger.Debug("failed to close ACP stream websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
m.logger.Info("ACP stream WebSocket connected")
|
||||
|
||||
updatesCh := m.singleProcMgr.GetUpdates()
|
||||
|
||||
for {
|
||||
select {
|
||||
case notification, ok := <-updatesCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for notification := range updatesCh {
|
||||
data, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
m.logger.Error("failed to marshal notification", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
m.logger.Error("failed to marshal notification", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
return
|
||||
}
|
||||
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -458,29 +455,26 @@ func (m *ControlServer) handlePermissionStreamWS(c *gin.Context) {
|
||||
m.logger.Error("WebSocket upgrade failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
m.logger.Debug("failed to close permission websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
m.logger.Info("Permission stream WebSocket connected")
|
||||
|
||||
permissionCh := m.singleProcMgr.GetPermissionRequests()
|
||||
|
||||
for {
|
||||
select {
|
||||
case notification, ok := <-permissionCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for notification := range permissionCh {
|
||||
data, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
m.logger.Error("failed to marshal permission notification", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
m.logger.Error("failed to marshal permission notification", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
m.logger.Debug("WebSocket write error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
m.logger.Debug("WebSocket write error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,7 +517,11 @@ func (m *ControlServer) handleGitStatusStreamWS(c *gin.Context) {
|
||||
m.logger.Error("WebSocket upgrade failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
m.logger.Debug("failed to close git status websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
sub := m.singleProcMgr.GetWorkspaceTracker().SubscribeGitStatus()
|
||||
defer m.singleProcMgr.GetWorkspaceTracker().UnsubscribeGitStatus(sub)
|
||||
@@ -565,7 +563,11 @@ func (m *ControlServer) handleFilesStreamWS(c *gin.Context) {
|
||||
m.logger.Error("WebSocket upgrade failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
m.logger.Debug("failed to close files websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
sub := m.singleProcMgr.GetWorkspaceTracker().SubscribeFiles()
|
||||
defer m.singleProcMgr.GetWorkspaceTracker().UnsubscribeFiles(sub)
|
||||
@@ -607,7 +609,11 @@ func (m *ControlServer) handleFileChangesStreamWS(c *gin.Context) {
|
||||
m.logger.Error("WebSocket upgrade failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
m.logger.Debug("failed to close file changes websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
sub := m.singleProcMgr.GetWorkspaceTracker().SubscribeFileChanges()
|
||||
defer m.singleProcMgr.GetWorkspaceTracker().UnsubscribeFileChanges(sub)
|
||||
@@ -675,4 +681,4 @@ func (m *ControlServer) handleFileContent(c *gin.Context) {
|
||||
}
|
||||
|
||||
c.JSON(200, process.FileContentResponse{Path: path, Content: content, Size: size})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ func (s *Server) handleGitStatusStreamWS(c *gin.Context) {
|
||||
s.logger.Error("WebSocket upgrade failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
s.logger.Debug("failed to close git status websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Subscribe to git status updates
|
||||
sub := s.procMgr.GetWorkspaceTracker().SubscribeGitStatus()
|
||||
@@ -62,7 +66,11 @@ func (s *Server) handleFilesStreamWS(c *gin.Context) {
|
||||
s.logger.Error("WebSocket upgrade failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
s.logger.Debug("failed to close files websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Subscribe to file updates
|
||||
sub := s.procMgr.GetWorkspaceTracker().SubscribeFiles()
|
||||
@@ -108,7 +116,11 @@ func (s *Server) handleFileChangesStreamWS(c *gin.Context) {
|
||||
s.logger.Error("WebSocket upgrade failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
s.logger.Debug("failed to close file changes websocket", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Subscribe to file change notifications
|
||||
sub := s.procMgr.GetWorkspaceTracker().SubscribeFileChanges()
|
||||
@@ -186,6 +198,8 @@ func (s *Server) handleFileContent(c *gin.Context) {
|
||||
// mustParseInt parses a string to int, returns 0 on error
|
||||
func mustParseInt(s string) int {
|
||||
var n int
|
||||
json.Unmarshal([]byte(s), &n)
|
||||
if err := json.Unmarshal([]byte(s), &n); err != nil {
|
||||
return 0
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -124,7 +124,9 @@ func (m *Manager) CreateInstance(ctx context.Context, req *CreateRequest) (*Crea
|
||||
// Default to a simple handler if no factory is set
|
||||
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
w.Write([]byte("server factory not configured"))
|
||||
if _, err := w.Write([]byte("server factory not configured")); err != nil {
|
||||
m.logger.Debug("failed to write default response", zap.Error(err))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -277,4 +279,3 @@ func collectEnvForInstance(env map[string]string) []string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,9 @@ func (m *Manager) Stop(ctx context.Context) error {
|
||||
|
||||
// Close the adapter
|
||||
if m.adapter != nil {
|
||||
m.adapter.Close()
|
||||
if err := m.adapter.Close(); err != nil {
|
||||
m.logger.Debug("failed to close adapter", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// Close stop channel to signal readers
|
||||
@@ -322,7 +324,9 @@ func (m *Manager) Stop(ctx context.Context) error {
|
||||
|
||||
// Close stdin to signal EOF to agent
|
||||
if m.stdin != nil {
|
||||
m.stdin.Close()
|
||||
if err := m.stdin.Close(); err != nil {
|
||||
m.logger.Debug("failed to close stdin", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for process to exit with timeout
|
||||
@@ -339,7 +343,9 @@ func (m *Manager) Stop(ctx context.Context) error {
|
||||
// Force kill
|
||||
if m.cmd != nil && m.cmd.Process != nil {
|
||||
m.logger.Warn("force killing agent process")
|
||||
m.cmd.Process.Kill()
|
||||
if err := m.cmd.Process.Kill(); err != nil {
|
||||
m.logger.Warn("failed to kill agent process", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,4 +569,3 @@ func (m *Manager) GetPendingPermissions() []*PendingPermission {
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -86,7 +86,9 @@ func (wt *WorkspaceTracker) Start(ctx context.Context) {
|
||||
func (wt *WorkspaceTracker) Stop() {
|
||||
close(wt.stopCh)
|
||||
if wt.watcher != nil {
|
||||
wt.watcher.Close()
|
||||
if err := wt.watcher.Close(); err != nil {
|
||||
wt.logger.Debug("failed to close watcher", zap.Error(err))
|
||||
}
|
||||
}
|
||||
wt.wg.Wait()
|
||||
wt.logger.Info("workspace tracker stopped")
|
||||
@@ -611,7 +613,9 @@ func (wt *WorkspaceTracker) watchFilesystem(ctx context.Context) {
|
||||
// If a directory was created, watch it
|
||||
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||
if info, err := os.Stat(event.Name); err == nil && info.IsDir() {
|
||||
wt.addDirectoryRecursive(event.Name)
|
||||
if err := wt.addDirectoryRecursive(event.Name); err != nil {
|
||||
wt.logger.Debug("failed to watch new directory", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,7 +756,9 @@ func (wt *WorkspaceTracker) GetFileContent(reqPath string) (string, int64, error
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
@@ -763,4 +769,3 @@ func (wt *WorkspaceTracker) GetFileContent(reqPath string) (string, int64, error
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -226,7 +226,9 @@ func (b *MemoryEventBus) Request(ctx context.Context, subject string, event *Eve
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create reply subscription: %w", err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
defer func() {
|
||||
_ = sub.Unsubscribe()
|
||||
}()
|
||||
|
||||
// Add reply subject to event data
|
||||
if event.Data == nil {
|
||||
|
||||
@@ -49,7 +49,9 @@ func TestMemoryEventBus_PublishSubscribe(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Subscribe failed: %v", err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
defer func() {
|
||||
_ = sub.Unsubscribe()
|
||||
}()
|
||||
|
||||
event := NewEvent("test.type", "test-source", map[string]interface{}{"key": "value"})
|
||||
if err := bus.Publish(ctx, "test.subject", event); err != nil {
|
||||
@@ -86,7 +88,9 @@ func TestMemoryEventBus_MultipleSubscribers(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Subscribe %d failed: %v", i, err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
defer func() {
|
||||
_ = sub.Unsubscribe()
|
||||
}()
|
||||
}
|
||||
|
||||
event := NewEvent("test.type", "test-source", nil)
|
||||
@@ -159,7 +163,9 @@ func TestMemoryEventBus_SingleTokenWildcard(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Subscribe failed: %v", err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
defer func() {
|
||||
_ = sub.Unsubscribe()
|
||||
}()
|
||||
|
||||
// Should match - "user" fills the * slot
|
||||
event1 := NewEvent("user.created", "test", nil)
|
||||
@@ -201,7 +207,9 @@ func TestMemoryEventBus_MultiTokenWildcard(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Subscribe failed: %v", err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
defer func() {
|
||||
_ = sub.Unsubscribe()
|
||||
}()
|
||||
|
||||
// Should match - single remaining token
|
||||
event1 := NewEvent("email", "test", nil)
|
||||
@@ -238,7 +246,9 @@ func TestMemoryEventBus_WildcardNoMatch(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Subscribe failed: %v", err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
defer func() {
|
||||
_ = sub.Unsubscribe()
|
||||
}()
|
||||
|
||||
// This should NOT match - missing middle token
|
||||
event := NewEvent("test", "test", nil)
|
||||
@@ -269,7 +279,9 @@ func TestMemoryEventBus_ExactMatch(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Subscribe failed: %v", err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
defer func() {
|
||||
_ = sub.Unsubscribe()
|
||||
}()
|
||||
|
||||
// Should match exactly
|
||||
event1 := NewEvent("test", "test", nil)
|
||||
@@ -312,7 +324,9 @@ func TestMemoryEventBus_QueueSubscribe(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("QueueSubscribe %d failed: %v", i, err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
defer func() {
|
||||
_ = sub.Unsubscribe()
|
||||
}()
|
||||
}
|
||||
|
||||
// Publish multiple events
|
||||
@@ -338,6 +352,7 @@ func TestMemoryEventBus_ConcurrentAccess(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
var receivedCount int32
|
||||
var publishErrorCount int32
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Subscribe
|
||||
@@ -348,7 +363,9 @@ func TestMemoryEventBus_ConcurrentAccess(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Subscribe failed: %v", err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
defer func() {
|
||||
_ = sub.Unsubscribe()
|
||||
}()
|
||||
|
||||
// Publish concurrently from multiple goroutines
|
||||
numGoroutines := 10
|
||||
@@ -360,12 +377,17 @@ func TestMemoryEventBus_ConcurrentAccess(t *testing.T) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < eventsPerGoroutine; j++ {
|
||||
event := NewEvent("test.type", "test-source", nil)
|
||||
bus.Publish(ctx, "test.concurrent", event)
|
||||
if err := bus.Publish(ctx, "test.concurrent", event); err != nil {
|
||||
atomic.AddInt32(&publishErrorCount, 1)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
if publishErrorCount > 0 {
|
||||
t.Errorf("publish errors: %d", publishErrorCount)
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond) // Allow handlers to complete
|
||||
|
||||
expectedCount := int32(numGoroutines * eventsPerGoroutine)
|
||||
@@ -426,7 +448,9 @@ func TestMemoryEventBus_Request(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Subscribe failed: %v", err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
defer func() {
|
||||
_ = sub.Unsubscribe()
|
||||
}()
|
||||
|
||||
// Make a request
|
||||
request := NewEvent("echo.request", "requester", map[string]interface{}{
|
||||
@@ -484,4 +508,3 @@ func TestNewEvent(t *testing.T) {
|
||||
t.Error("Expected timestamp to be set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,14 +57,17 @@ func NewClient(id string, conn *websocket.Conn, hub *Hub, log *logger.Logger) *C
|
||||
func (c *Client) ReadPump(ctx context.Context) {
|
||||
defer func() {
|
||||
c.hub.Unregister(c)
|
||||
c.conn.Close()
|
||||
if err := c.conn.Close(); err != nil {
|
||||
c.logger.Debug("failed to close websocket connection", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
c.conn.SetReadLimit(maxMessageSize)
|
||||
c.conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||
if err := c.conn.SetReadDeadline(time.Now().Add(pongWait)); err != nil {
|
||||
c.logger.Debug("failed to set read deadline", zap.Error(err))
|
||||
}
|
||||
c.conn.SetPongHandler(func(string) error {
|
||||
c.conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||
return nil
|
||||
return c.conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||
})
|
||||
|
||||
for {
|
||||
@@ -310,16 +313,22 @@ func (c *Client) WritePump() {
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
c.conn.Close()
|
||||
if err := c.conn.Close(); err != nil {
|
||||
c.logger.Debug("failed to close websocket connection", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.send:
|
||||
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if err := c.conn.SetWriteDeadline(time.Now().Add(writeWait)); err != nil {
|
||||
c.logger.Debug("failed to set write deadline", zap.Error(err))
|
||||
}
|
||||
if !ok {
|
||||
// Hub closed the channel
|
||||
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
if err := c.conn.WriteMessage(websocket.CloseMessage, []byte{}); err != nil {
|
||||
c.logger.Debug("failed to write close message", zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -327,13 +336,25 @@ func (c *Client) WritePump() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
w.Write(message)
|
||||
if _, err := w.Write(message); err != nil {
|
||||
c.logger.Debug("failed to write websocket message", zap.Error(err))
|
||||
_ = w.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Batch additional queued messages
|
||||
n := len(c.send)
|
||||
for i := 0; i < n; i++ {
|
||||
w.Write([]byte{'\n'})
|
||||
w.Write(<-c.send)
|
||||
if _, err := w.Write([]byte{'\n'}); err != nil {
|
||||
c.logger.Debug("failed to write websocket delimiter", zap.Error(err))
|
||||
_ = w.Close()
|
||||
return
|
||||
}
|
||||
if _, err := w.Write(<-c.send); err != nil {
|
||||
c.logger.Debug("failed to write queued websocket message", zap.Error(err))
|
||||
_ = w.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
@@ -341,7 +362,9 @@ func (c *Client) WritePump() {
|
||||
}
|
||||
|
||||
case <-ticker.C:
|
||||
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if err := c.conn.SetWriteDeadline(time.Now().Add(writeWait)); err != nil {
|
||||
c.logger.Debug("failed to set write deadline", zap.Error(err))
|
||||
}
|
||||
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,7 +61,11 @@ func NewTestServer(t *testing.T) *TestServer {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test repository: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { taskRepo.Close() })
|
||||
t.Cleanup(func() {
|
||||
if err := taskRepo.Close(); err != nil {
|
||||
t.Errorf("failed to close task repo: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
// Initialize task service
|
||||
taskSvc := taskservice.NewService(taskRepo, eventBus, log, taskservice.RepositoryDiscoveryConfig{})
|
||||
@@ -109,7 +113,9 @@ func NewTestServer(t *testing.T) *TestServer {
|
||||
func (ts *TestServer) Close() {
|
||||
ts.cancelFunc()
|
||||
ts.Server.Close()
|
||||
ts.TaskRepo.Close()
|
||||
if err := ts.TaskRepo.Close(); err != nil {
|
||||
ts.Logger.Error("failed to close task repo: " + err.Error())
|
||||
}
|
||||
ts.EventBus.Close()
|
||||
}
|
||||
|
||||
@@ -216,7 +222,9 @@ func (c *WSClient) readPump() {
|
||||
|
||||
// Close closes the WebSocket connection
|
||||
func (c *WSClient) Close() {
|
||||
c.conn.Close()
|
||||
if err := c.conn.Close(); err != nil {
|
||||
c.t.Logf("failed to close websocket: %v", err)
|
||||
}
|
||||
<-c.done
|
||||
}
|
||||
|
||||
@@ -476,7 +484,9 @@ func TestTaskCRUD(t *testing.T) {
|
||||
// Debug: print error if there is one
|
||||
if resp.Type == ws.MessageTypeError {
|
||||
var errPayload ws.ErrorPayload
|
||||
resp.ParsePayload(&errPayload)
|
||||
if err := resp.ParsePayload(&errPayload); err != nil {
|
||||
t.Fatalf("failed to parse error payload: %v", err)
|
||||
}
|
||||
t.Logf("Error response: %+v", errPayload)
|
||||
}
|
||||
|
||||
@@ -587,7 +597,9 @@ func TestTaskStateTransitions(t *testing.T) {
|
||||
"name": "State Test Board",
|
||||
})
|
||||
var boardPayload map[string]interface{}
|
||||
boardResp.ParsePayload(&boardPayload)
|
||||
if err := boardResp.ParsePayload(&boardPayload); err != nil {
|
||||
t.Fatalf("failed to parse board payload: %v", err)
|
||||
}
|
||||
boardID := boardPayload["id"].(string)
|
||||
|
||||
colResp, _ := client.SendRequest("col-1", ws.ActionColumnCreate, map[string]interface{}{
|
||||
@@ -596,7 +608,9 @@ func TestTaskStateTransitions(t *testing.T) {
|
||||
"position": 0,
|
||||
})
|
||||
var colPayload map[string]interface{}
|
||||
colResp.ParsePayload(&colPayload)
|
||||
if err := colResp.ParsePayload(&colPayload); err != nil {
|
||||
t.Fatalf("failed to parse column payload: %v", err)
|
||||
}
|
||||
columnID := colPayload["id"].(string)
|
||||
|
||||
taskResp, _ := client.SendRequest("task-1", ws.ActionTaskCreate, map[string]interface{}{
|
||||
@@ -609,7 +623,9 @@ func TestTaskStateTransitions(t *testing.T) {
|
||||
"base_branch": "main",
|
||||
})
|
||||
var taskPayload map[string]interface{}
|
||||
taskResp.ParsePayload(&taskPayload)
|
||||
if err := taskResp.ParsePayload(&taskPayload); err != nil {
|
||||
t.Fatalf("failed to parse task payload: %v", err)
|
||||
}
|
||||
taskID := taskPayload["id"].(string)
|
||||
|
||||
// Test state transitions
|
||||
@@ -665,7 +681,9 @@ func TestTaskMove(t *testing.T) {
|
||||
"name": "Move Test Board",
|
||||
})
|
||||
var boardPayload map[string]interface{}
|
||||
boardResp.ParsePayload(&boardPayload)
|
||||
if err := boardResp.ParsePayload(&boardPayload); err != nil {
|
||||
t.Fatalf("failed to parse board payload: %v", err)
|
||||
}
|
||||
boardID := boardPayload["id"].(string)
|
||||
|
||||
col1Resp, _ := client.SendRequest("col-1", ws.ActionColumnCreate, map[string]interface{}{
|
||||
@@ -674,7 +692,9 @@ func TestTaskMove(t *testing.T) {
|
||||
"position": 0,
|
||||
})
|
||||
var col1Payload map[string]interface{}
|
||||
col1Resp.ParsePayload(&col1Payload)
|
||||
if err := col1Resp.ParsePayload(&col1Payload); err != nil {
|
||||
t.Fatalf("failed to parse column payload: %v", err)
|
||||
}
|
||||
col1ID := col1Payload["id"].(string)
|
||||
|
||||
col2Resp, _ := client.SendRequest("col-2", ws.ActionColumnCreate, map[string]interface{}{
|
||||
@@ -683,7 +703,9 @@ func TestTaskMove(t *testing.T) {
|
||||
"position": 1,
|
||||
})
|
||||
var col2Payload map[string]interface{}
|
||||
col2Resp.ParsePayload(&col2Payload)
|
||||
if err := col2Resp.ParsePayload(&col2Payload); err != nil {
|
||||
t.Fatalf("failed to parse column payload: %v", err)
|
||||
}
|
||||
col2ID := col2Payload["id"].(string)
|
||||
|
||||
// Create task in first column
|
||||
@@ -696,7 +718,9 @@ func TestTaskMove(t *testing.T) {
|
||||
"base_branch": "main",
|
||||
})
|
||||
var taskPayload map[string]interface{}
|
||||
taskResp.ParsePayload(&taskPayload)
|
||||
if err := taskResp.ParsePayload(&taskPayload); err != nil {
|
||||
t.Fatalf("failed to parse task payload: %v", err)
|
||||
}
|
||||
taskID := taskPayload["id"].(string)
|
||||
|
||||
// Move task to second column
|
||||
@@ -913,7 +937,9 @@ func TestTaskSubscription(t *testing.T) {
|
||||
"name": "Sub Test Board",
|
||||
})
|
||||
var boardPayload map[string]interface{}
|
||||
boardResp.ParsePayload(&boardPayload)
|
||||
if err := boardResp.ParsePayload(&boardPayload); err != nil {
|
||||
t.Fatalf("failed to parse board payload: %v", err)
|
||||
}
|
||||
boardID := boardPayload["id"].(string)
|
||||
|
||||
colResp, _ := client.SendRequest("col-1", ws.ActionColumnCreate, map[string]interface{}{
|
||||
@@ -922,7 +948,9 @@ func TestTaskSubscription(t *testing.T) {
|
||||
"position": 0,
|
||||
})
|
||||
var colPayload map[string]interface{}
|
||||
colResp.ParsePayload(&colPayload)
|
||||
if err := colResp.ParsePayload(&colPayload); err != nil {
|
||||
t.Fatalf("failed to parse column payload: %v", err)
|
||||
}
|
||||
columnID := colPayload["id"].(string)
|
||||
|
||||
taskResp, _ := client.SendRequest("task-1", ws.ActionTaskCreate, map[string]interface{}{
|
||||
@@ -934,7 +962,9 @@ func TestTaskSubscription(t *testing.T) {
|
||||
"base_branch": "main",
|
||||
})
|
||||
var taskPayload map[string]interface{}
|
||||
taskResp.ParsePayload(&taskPayload)
|
||||
if err := taskResp.ParsePayload(&taskPayload); err != nil {
|
||||
t.Fatalf("failed to parse task payload: %v", err)
|
||||
}
|
||||
taskID := taskPayload["id"].(string)
|
||||
|
||||
// Subscribe to the task
|
||||
@@ -989,7 +1019,9 @@ func TestConcurrentRequests(t *testing.T) {
|
||||
"name": "Concurrent Test Board",
|
||||
})
|
||||
var boardPayload map[string]interface{}
|
||||
boardResp.ParsePayload(&boardPayload)
|
||||
if err := boardResp.ParsePayload(&boardPayload); err != nil {
|
||||
t.Fatalf("failed to parse board payload: %v", err)
|
||||
}
|
||||
boardID := boardPayload["id"].(string)
|
||||
|
||||
colResp, _ := client.SendRequest("col-1", ws.ActionColumnCreate, map[string]interface{}{
|
||||
@@ -998,7 +1030,9 @@ func TestConcurrentRequests(t *testing.T) {
|
||||
"position": 0,
|
||||
})
|
||||
var colPayload map[string]interface{}
|
||||
colResp.ParsePayload(&colPayload)
|
||||
if err := colResp.ParsePayload(&colPayload); err != nil {
|
||||
t.Fatalf("failed to parse column payload: %v", err)
|
||||
}
|
||||
columnID := colPayload["id"].(string)
|
||||
|
||||
// Create multiple tasks concurrently
|
||||
|
||||
@@ -340,7 +340,9 @@ func (s *SimulatedAgentManagerClient) PromptAgent(ctx context.Context, agentInst
|
||||
|
||||
event := bus.NewEvent(events.ACPMessage, "simulated-agent", msgData)
|
||||
subject := events.BuildACPSubject(instance.taskID)
|
||||
s.eventBus.Publish(context.Background(), subject, event)
|
||||
if err := s.eventBus.Publish(context.Background(), subject, event); err != nil {
|
||||
s.logger.Warn("failed to publish simulated ACP message", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
return &executor.PromptResult{
|
||||
@@ -474,7 +476,11 @@ func NewOrchestratorTestServer(t *testing.T) *OrchestratorTestServer {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test repository: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { taskRepo.Close() })
|
||||
t.Cleanup(func() {
|
||||
if err := taskRepo.Close(); err != nil {
|
||||
t.Errorf("failed to close task repo: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
// Initialize task service
|
||||
taskSvc := taskservice.NewService(taskRepo, eventBus, log, taskservice.RepositoryDiscoveryConfig{})
|
||||
@@ -554,11 +560,15 @@ func NewOrchestratorTestServer(t *testing.T) *OrchestratorTestServer {
|
||||
|
||||
// Close shuts down the test server
|
||||
func (ts *OrchestratorTestServer) Close() {
|
||||
ts.OrchestratorSvc.Stop()
|
||||
if err := ts.OrchestratorSvc.Stop(); err != nil {
|
||||
ts.Logger.Warn("failed to stop orchestrator", zap.Error(err))
|
||||
}
|
||||
ts.AgentManager.Close()
|
||||
ts.cancelFunc()
|
||||
ts.Server.Close()
|
||||
ts.TaskRepo.Close()
|
||||
if err := ts.TaskRepo.Close(); err != nil {
|
||||
ts.Logger.Warn("failed to close task repo", zap.Error(err))
|
||||
}
|
||||
ts.EventBus.Close()
|
||||
}
|
||||
|
||||
@@ -657,8 +667,7 @@ func createOrchestratorWorkspace(t *testing.T, client *OrchestratorWSClient) str
|
||||
require.NoError(t, err)
|
||||
|
||||
var payload map[string]interface{}
|
||||
err = resp.ParsePayload(&payload)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, resp.ParsePayload(&payload))
|
||||
|
||||
return payload["id"].(string)
|
||||
}
|
||||
@@ -693,7 +702,9 @@ func (c *OrchestratorWSClient) readPump() {
|
||||
|
||||
// Close closes the WebSocket connection
|
||||
func (c *OrchestratorWSClient) Close() {
|
||||
c.conn.Close()
|
||||
if err := c.conn.Close(); err != nil {
|
||||
c.t.Logf("failed to close websocket: %v", err)
|
||||
}
|
||||
<-c.done
|
||||
}
|
||||
|
||||
@@ -1020,7 +1031,10 @@ func TestOrchestratorConcurrentTasks(t *testing.T) {
|
||||
}
|
||||
|
||||
var payload map[string]interface{}
|
||||
resp.ParsePayload(&payload)
|
||||
if err := resp.ParsePayload(&payload); err != nil {
|
||||
t.Logf("failed to parse start response: %v", err)
|
||||
return
|
||||
}
|
||||
results <- payload
|
||||
}(i, taskID)
|
||||
}
|
||||
|
||||
@@ -126,7 +126,11 @@ func createTestExecutor(t *testing.T, agentMgr *mockAgentManager, log *logger.Lo
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test repository: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { repo.Close() })
|
||||
t.Cleanup(func() {
|
||||
if err := repo.Close(); err != nil {
|
||||
t.Errorf("failed to close repo: %v", err)
|
||||
}
|
||||
})
|
||||
cfg := executor.ExecutorConfig{
|
||||
MaxConcurrent: maxConcurrent,
|
||||
WorktreeEnabled: true,
|
||||
@@ -215,7 +219,9 @@ func TestStartAlreadyRunning(t *testing.T) {
|
||||
s := NewScheduler(q, exec, taskRepo, log, DefaultSchedulerConfig())
|
||||
|
||||
_ = s.Start(context.Background())
|
||||
defer s.Stop()
|
||||
defer func() {
|
||||
_ = s.Stop()
|
||||
}()
|
||||
|
||||
err := s.Start(context.Background())
|
||||
if err != ErrSchedulerAlreadyRunning {
|
||||
|
||||
@@ -204,7 +204,9 @@ func (s *Service) Start(ctx context.Context) error {
|
||||
|
||||
// Start the scheduler processing loop
|
||||
if err := s.scheduler.Start(ctx); err != nil {
|
||||
s.watcher.Stop()
|
||||
if stopErr := s.watcher.Stop(); stopErr != nil {
|
||||
s.logger.Warn("failed to stop watcher after scheduler start failure", zap.Error(stopErr))
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.running = false
|
||||
s.mu.Unlock()
|
||||
@@ -640,7 +642,7 @@ func buildACPMetadata(msg *protocol.Message) map[string]interface{} {
|
||||
"provider_type": string(msg.Type),
|
||||
"provider_agent": msg.AgentID,
|
||||
}
|
||||
if msg.Data != nil && len(msg.Data) > 0 {
|
||||
if len(msg.Data) > 0 {
|
||||
metadata["provider_data"] = msg.Data
|
||||
}
|
||||
return metadata
|
||||
|
||||
@@ -59,7 +59,9 @@ func (b *mockEventBus) Publish(ctx context.Context, subject string, event *bus.E
|
||||
// Call handlers for matching subjects
|
||||
if handlers, ok := b.subscriptions[subject]; ok {
|
||||
for _, handler := range handlers {
|
||||
go handler(ctx, event)
|
||||
go func(handler bus.EventHandler) {
|
||||
_ = handler(ctx, event)
|
||||
}(handler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +69,9 @@ func (b *mockEventBus) Publish(ctx context.Context, subject string, event *bus.E
|
||||
for _, queueGroup := range b.queuedSubs {
|
||||
if handlers, ok := queueGroup[subject]; ok && len(handlers) > 0 {
|
||||
// Deliver to first handler (simplified)
|
||||
go handlers[0](ctx, event)
|
||||
go func(handler bus.EventHandler) {
|
||||
_ = handler(ctx, event)
|
||||
}(handlers[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +221,9 @@ func TestTaskEventHandling(t *testing.T) {
|
||||
|
||||
w := NewWatcher(eventBus, handlers, log)
|
||||
_ = w.Start(context.Background())
|
||||
defer w.Stop()
|
||||
defer func() {
|
||||
_ = w.Stop()
|
||||
}()
|
||||
|
||||
// Simulate publishing a task state changed event
|
||||
oldState := v1.TaskStateTODO
|
||||
@@ -262,7 +268,9 @@ func TestAgentEventHandling(t *testing.T) {
|
||||
|
||||
w := NewWatcher(eventBus, handlers, log)
|
||||
_ = w.Start(context.Background())
|
||||
defer w.Stop()
|
||||
defer func() {
|
||||
_ = w.Stop()
|
||||
}()
|
||||
|
||||
// Simulate publishing an agent completed event
|
||||
exitCode := 0
|
||||
@@ -312,7 +320,9 @@ func TestACPMessageHandling(t *testing.T) {
|
||||
|
||||
w := NewWatcher(eventBus, handlers, log)
|
||||
_ = w.Start(context.Background())
|
||||
defer w.Stop()
|
||||
defer func() {
|
||||
_ = w.Stop()
|
||||
}()
|
||||
|
||||
// Simulate publishing an ACP message
|
||||
event := bus.NewEvent(events.BuildACPSubject("task-789"), "test", map[string]interface{}{
|
||||
@@ -407,4 +417,3 @@ func TestIsRunning(t *testing.T) {
|
||||
t.Error("watcher should not be running after Stop")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -626,15 +626,6 @@ func isSessionActive(status models.AgentSessionState) bool {
|
||||
status == models.AgentSessionStateStarting
|
||||
}
|
||||
|
||||
func (r *MemoryRepository) getTaskPlacement(taskID, boardID string) (*taskPlacement, bool) {
|
||||
placements, ok := r.taskPlacements[taskID]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
placement, ok := placements[boardID]
|
||||
return placement, ok
|
||||
}
|
||||
|
||||
// Message operations
|
||||
|
||||
// CreateMessage creates a new message
|
||||
|
||||
@@ -48,7 +48,9 @@ func NewSQLiteRepository(dbPath string) (*SQLiteRepository, error) {
|
||||
|
||||
// Initialize schema
|
||||
if err := repo.initSchema(); err != nil {
|
||||
db.Close()
|
||||
if closeErr := db.Close(); closeErr != nil {
|
||||
return nil, fmt.Errorf("failed to close database after schema error: %w", closeErr)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to initialize schema: %w", err)
|
||||
}
|
||||
|
||||
@@ -342,7 +344,7 @@ func (r *SQLiteRepository) columnExists(table, column string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
for rows.Next() {
|
||||
var cid int
|
||||
@@ -589,26 +591,6 @@ func (r *SQLiteRepository) ensureWorkspaceIndexes() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SQLiteRepository) backfillTaskMappings() error {
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := r.db.ExecContext(ctx, `
|
||||
INSERT OR IGNORE INTO task_boards (task_id, board_id)
|
||||
SELECT id, board_id FROM tasks WHERE board_id != ''
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.db.ExecContext(ctx, `
|
||||
INSERT OR IGNORE INTO task_columns (task_id, board_id, column_id, position)
|
||||
SELECT id, board_id, column_id, position
|
||||
FROM tasks
|
||||
WHERE board_id != '' AND column_id != ''
|
||||
`)
|
||||
return err
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func (r *SQLiteRepository) Close() error {
|
||||
return r.db.Close()
|
||||
@@ -733,7 +715,7 @@ func (r *SQLiteRepository) ListWorkspaces(ctx context.Context) ([]*models.Worksp
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var result []*models.Workspace
|
||||
for rows.Next() {
|
||||
@@ -794,7 +776,9 @@ func (r *SQLiteRepository) CreateTask(ctx context.Context, task *models.Task) er
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, task.ID, task.WorkspaceID, task.BoardID, task.ColumnID, task.Title, task.Description, task.State, task.Priority, task.RepositoryID, task.BaseBranch, task.AssignedTo, task.Position, string(metadata), task.CreatedAt, task.UpdatedAt)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
return fmt.Errorf("failed to rollback task insert: %w", rollbackErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -875,7 +859,7 @@ func (r *SQLiteRepository) ListTasks(ctx context.Context, boardID string) ([]*mo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
return r.scanTasks(rows)
|
||||
}
|
||||
@@ -890,7 +874,7 @@ func (r *SQLiteRepository) ListTasksByColumn(ctx context.Context, columnID strin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
return r.scanTasks(rows)
|
||||
}
|
||||
@@ -960,13 +944,6 @@ func (r *SQLiteRepository) RemoveTaskFromBoard(ctx context.Context, taskID, boar
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *SQLiteRepository) addTaskToBoardTx(ctx context.Context, tx *sql.Tx, taskID, boardID, columnID string, position int) error {
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
UPDATE tasks SET board_id = ?, column_id = ?, position = ?, updated_at = ? WHERE id = ?
|
||||
`, boardID, columnID, position, time.Now().UTC(), taskID)
|
||||
return err
|
||||
}
|
||||
|
||||
// Board operations
|
||||
|
||||
// CreateBoard creates a new board
|
||||
@@ -1049,7 +1026,7 @@ func (r *SQLiteRepository) ListBoards(ctx context.Context, workspaceID string) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var result []*models.Board
|
||||
for rows.Next() {
|
||||
@@ -1153,7 +1130,7 @@ func (r *SQLiteRepository) ListColumns(ctx context.Context, boardID string) ([]*
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var result []*models.Column
|
||||
for rows.Next() {
|
||||
@@ -1242,7 +1219,7 @@ func (r *SQLiteRepository) ListMessages(ctx context.Context, sessionID string) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var result []*models.Message
|
||||
for rows.Next() {
|
||||
@@ -1327,7 +1304,7 @@ func (r *SQLiteRepository) ListMessagesPaginated(ctx context.Context, sessionID
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var result []*models.Message
|
||||
for rows.Next() {
|
||||
@@ -1516,7 +1493,7 @@ func (r *SQLiteRepository) ListRepositories(ctx context.Context, workspaceID str
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var result []*models.Repository
|
||||
for rows.Next() {
|
||||
@@ -1604,7 +1581,7 @@ func (r *SQLiteRepository) ListRepositoryScripts(ctx context.Context, repository
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var result []*models.RepositoryScript
|
||||
for rows.Next() {
|
||||
@@ -1949,7 +1926,7 @@ func (r *SQLiteRepository) ListAgentSessions(ctx context.Context, taskID string)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
return r.scanAgentSessions(rows)
|
||||
}
|
||||
@@ -1966,7 +1943,7 @@ func (r *SQLiteRepository) ListActiveAgentSessions(ctx context.Context) ([]*mode
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
return r.scanAgentSessions(rows)
|
||||
}
|
||||
@@ -2194,7 +2171,7 @@ func (r *SQLiteRepository) ListExecutors(ctx context.Context) ([]*models.Executo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var result []*models.Executor
|
||||
for rows.Next() {
|
||||
@@ -2313,7 +2290,7 @@ func (r *SQLiteRepository) ListEnvironments(ctx context.Context) ([]*models.Envi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var result []*models.Environment
|
||||
for rows.Next() {
|
||||
|
||||
@@ -22,8 +22,12 @@ func createTestSQLiteRepo(t *testing.T) (*SQLiteRepository, func()) {
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
repo.Close()
|
||||
os.Remove(dbPath)
|
||||
if err := repo.Close(); err != nil {
|
||||
t.Errorf("failed to close repo: %v", err)
|
||||
}
|
||||
if err := os.Remove(dbPath); err != nil && !os.IsNotExist(err) {
|
||||
t.Errorf("failed to remove db path: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return repo, cleanup
|
||||
@@ -475,14 +479,20 @@ func TestSQLiteRepository_Persistence(t *testing.T) {
|
||||
_ = repo1.CreateWorkspace(ctx, &models.Workspace{ID: "ws-1", Name: "Workspace"})
|
||||
board := &models.Board{ID: "persist-board", WorkspaceID: "ws-1", Name: "Persistent Board"}
|
||||
_ = repo1.CreateBoard(ctx, board)
|
||||
repo1.Close()
|
||||
if err := repo1.Close(); err != nil {
|
||||
t.Fatalf("failed to close repo: %v", err)
|
||||
}
|
||||
|
||||
// Reopen repository and verify data persisted
|
||||
repo2, err := NewSQLiteRepository(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create second repository: %v", err)
|
||||
}
|
||||
defer repo2.Close()
|
||||
defer func() {
|
||||
if err := repo2.Close(); err != nil {
|
||||
t.Errorf("failed to close repo: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
retrieved, err := repo2.GetBoard(ctx, "persist-board")
|
||||
if err != nil {
|
||||
|
||||
@@ -277,7 +277,11 @@ func newDiscoveryService(t *testing.T, root string) *Service {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test repository: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { repo.Close() })
|
||||
t.Cleanup(func() {
|
||||
if err := repo.Close(); err != nil {
|
||||
t.Errorf("failed to close repo: %v", err)
|
||||
}
|
||||
})
|
||||
log, _ := logger.NewLogger(logger.LoggingConfig{Level: "error", Format: "json", OutputPath: "stdout"})
|
||||
eventBus := bus.NewMemoryEventBus(log)
|
||||
return NewService(repo, eventBus, log, RepositoryDiscoveryConfig{
|
||||
|
||||
@@ -74,7 +74,11 @@ func createTestService(t *testing.T) (*Service, *MockEventBus, repository.Reposi
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test repository: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { repo.Close() })
|
||||
t.Cleanup(func() {
|
||||
if err := repo.Close(); err != nil {
|
||||
t.Errorf("failed to close repo: %v", err)
|
||||
}
|
||||
})
|
||||
eventBus := NewMockEventBus()
|
||||
log, _ := logger.NewLogger(logger.LoggingConfig{Level: "error", Format: "json", OutputPath: "stdout"})
|
||||
svc := NewService(repo, eventBus, log, RepositoryDiscoveryConfig{})
|
||||
|
||||
@@ -42,7 +42,9 @@ func NewSQLiteRepository(dbPath string) (*SQLiteRepository, error) {
|
||||
|
||||
repo := &SQLiteRepository{db: db}
|
||||
if err := repo.initSchema(); err != nil {
|
||||
db.Close()
|
||||
if closeErr := db.Close(); closeErr != nil {
|
||||
return nil, fmt.Errorf("failed to close database after schema error: %w", closeErr)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to initialize schema: %w", err)
|
||||
}
|
||||
return repo, nil
|
||||
@@ -112,7 +114,9 @@ func (r *SQLiteRepository) columnExists(table, column string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
var cid int
|
||||
|
||||
@@ -228,7 +228,8 @@ func (c *Client) handleRequest(id interface{}, method string, params json.RawMes
|
||||
c.onRequest(id, method, params)
|
||||
} else {
|
||||
c.logger.Warn("received request but no handler registered", zap.String("method", method))
|
||||
c.SendResponse(id, nil, &Error{Code: MethodNotFound, Message: "Method not found"})
|
||||
if err := c.SendResponse(id, nil, &Error{Code: MethodNotFound, Message: "Method not found"}); err != nil {
|
||||
c.logger.Warn("failed to send method not found response", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user