Files
kandev/apps/backend/internal/scriptengine/providers_test.go
T
João Salavisa 75f1732421 feat: add GitHub token injection for remote executors and Docker session resume (#654)
* feat: add GitHub token injection for remote executors and Docker session resume

- Add global GitHub access token configuration (backend + frontend)
- Inject GitHub token into Docker and Sprites executors via env vars and Git credential helper
- Transition Docker executor from host mounting to repository cloning
- Implement Docker session resume (reconnect to existing containers after backend restart)
- Add shell validation using exec.LookPath in agentctl
- Add scriptengine providers for agent install and agentctl placeholders

* fix: format github-status.tsx with prettier

* fix: resolve lint issues (goconst, errcheck, file-length)

* fix: address code review findings

- Fix resolveGHCLIToken to check both GITHUB_TOKEN and GH_TOKEN guards
- Fix credential helper to use \\ fallback
- Change /bin/bash to /bin/sh in credential helper for Alpine compat
- Strip embedded token from .git/config after clone in prepare scripts
- Extract buildLaunchAgentRequest helpers to stay under 80-line limit
- Add tests for GitHubAuthProvider, injectTokenIntoURL, reconnect validation
- Add GH_TOKEN-only test case for resolveGHCLIToken

* refactor(executor): extract GitHub credential resolution to separate file

Move injectGitHubToken, injectGitHubTokenFromCLI, resolveRemoteCredentials,
resolveAuthSecrets, methodIDToEnvVar, resolveGHCLIToken, and detectGHToken
out of executor_execute.go (was 803 logical lines, over the 800-line limit
after merging main) into a new executor_credentials.go file. Also adds the
nil-guard on secretStore in resolveAuthSecrets that was flagged by review.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 22:15:50 +01:00

126 lines
4.1 KiB
Go

package scriptengine
import (
"strings"
"testing"
)
func TestAgentInstallProvider(t *testing.T) {
t.Run("empty scripts produce empty placeholder", func(t *testing.T) {
provider := AgentInstallProvider(nil)
vars := provider()
if got := vars["kandev.agents.install"]; got != "" {
t.Fatalf("expected empty, got %q", got)
}
})
t.Run("deduplicates scripts", func(t *testing.T) {
scripts := []string{
"npm install -g @anthropic-ai/claude-code@2.1.50",
"npm install -g @openai/codex@0.104.0",
"npm install -g @anthropic-ai/claude-code@2.1.50",
}
provider := AgentInstallProvider(scripts)
vars := provider()
want := "npm install -g @anthropic-ai/claude-code@2.1.50\nnpm install -g @openai/codex@0.104.0"
if got := vars["kandev.agents.install"]; got != want {
t.Fatalf("got %q, want %q", got, want)
}
})
t.Run("trims whitespace and skips empty", func(t *testing.T) {
scripts := []string{" npm install -g foo ", "", " ", "npm install -g bar"}
provider := AgentInstallProvider(scripts)
vars := provider()
want := "npm install -g foo\nnpm install -g bar"
if got := vars["kandev.agents.install"]; got != want {
t.Fatalf("got %q, want %q", got, want)
}
})
}
func TestRepositoryProvider_UsesRepositorySetupScriptKey(t *testing.T) {
provider := RepositoryProvider(map[string]any{
"repository_path": "/tmp/repo",
"base_branch": "main",
"repository_setup_script": "npm ci",
"setup_script": "should-not-be-used",
}, nil, nil, nil)
vars := provider()
if got := vars["repository.setup_script"]; got != "npm ci" {
t.Fatalf("repository.setup_script = %q, want %q", got, "npm ci")
}
}
func TestGitHubAuthProvider(t *testing.T) {
t.Run("nil env returns fallback commands", func(t *testing.T) {
provider := GitHubAuthProvider(nil)
vars := provider()
setup := vars["github.auth_setup"]
if !strings.Contains(setup, "fallback") {
t.Errorf("expected fallback comment, got %q", setup)
}
if strings.Contains(setup, "credential") {
t.Error("expected no credential helper when no token")
}
})
t.Run("empty env returns fallback commands", func(t *testing.T) {
provider := GitHubAuthProvider(map[string]string{})
vars := provider()
setup := vars["github.auth_setup"]
if !strings.Contains(setup, "fallback") {
t.Errorf("expected fallback comment, got %q", setup)
}
})
t.Run("GH_TOKEN present configures credential helper", func(t *testing.T) {
provider := GitHubAuthProvider(map[string]string{"GH_TOKEN": "ghp_test123"})
vars := provider()
setup := vars["github.auth_setup"]
if !strings.Contains(setup, "credential.https://github.com.helper") {
t.Errorf("expected credential helper, got %q", setup)
}
// Must use /bin/sh, not /bin/bash
if strings.Contains(setup, "/bin/bash") {
t.Error("expected /bin/sh, not /bin/bash")
}
if !strings.Contains(setup, "/bin/sh") {
t.Error("expected /bin/sh in credential helper")
}
// Token must NOT be hardcoded in the output script
if strings.Contains(setup, "ghp_test123") {
t.Error("token value must not appear literally in the script")
}
// Must use env var fallback pattern
if !strings.Contains(setup, "${GH_TOKEN:-${GITHUB_TOKEN}}") {
t.Error("expected ${GH_TOKEN:-${GITHUB_TOKEN}} fallback pattern")
}
})
t.Run("GITHUB_TOKEN fallback configures credential helper", func(t *testing.T) {
provider := GitHubAuthProvider(map[string]string{"GITHUB_TOKEN": "ghp_fallback"})
vars := provider()
setup := vars["github.auth_setup"]
if !strings.Contains(setup, "credential.https://github.com.helper") {
t.Errorf("expected credential helper with GITHUB_TOKEN fallback, got %q", setup)
}
if strings.Contains(setup, "ghp_fallback") {
t.Error("token value must not appear literally in the script")
}
})
t.Run("includes gh CLI setup", func(t *testing.T) {
provider := GitHubAuthProvider(map[string]string{"GH_TOKEN": "ghp_test"})
vars := provider()
setup := vars["github.auth_setup"]
if !strings.Contains(setup, "gh config set git_protocol https") {
t.Error("expected gh CLI protocol config")
}
if !strings.Contains(setup, "gh auth setup-git") {
t.Error("expected gh auth setup-git backup")
}
})
}