180 lines
6.3 KiB
Go
180 lines
6.3 KiB
Go
package lifecycle
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
settingsmodels "github.com/kandev/kandev/internal/agent/settings/models"
|
|
"github.com/kandev/kandev/internal/common/logger"
|
|
"github.com/kandev/kandev/internal/githubauth"
|
|
"github.com/kandev/kandev/internal/secrets"
|
|
)
|
|
|
|
func TestMergeEnvFillMissing(t *testing.T) {
|
|
dst := map[string]string{"KANDEV_TASK_ID": "t1", "FOO": "existing"}
|
|
mergeEnvFillMissing(dst, map[string]string{"FOO": "new", "BAR": "added"})
|
|
if dst["FOO"] != "existing" {
|
|
t.Fatalf("expected FOO to remain existing, got %q", dst["FOO"])
|
|
}
|
|
if dst["BAR"] != "added" {
|
|
t.Fatalf("expected BAR=added, got %q", dst["BAR"])
|
|
}
|
|
}
|
|
|
|
func TestMergeEnvFillMissingComposesIndexedGitConfig(t *testing.T) {
|
|
dst := map[string]string{
|
|
"GIT_CONFIG_COUNT": "1",
|
|
"GIT_CONFIG_KEY_0": "credential.https://github.com.helper",
|
|
"GIT_CONFIG_VALUE_0": "!agentctl git-credential",
|
|
}
|
|
src := map[string]string{
|
|
"GIT_CONFIG_COUNT": "1",
|
|
"GIT_CONFIG_KEY_0": "core.hooksPath",
|
|
"GIT_CONFIG_VALUE_0": "/opt/locstat/hooks",
|
|
}
|
|
|
|
mergeEnvFillMissing(dst, src)
|
|
|
|
if got := dst["GIT_CONFIG_COUNT"]; got != "2" {
|
|
t.Fatalf("GIT_CONFIG_COUNT = %q, want 2", got)
|
|
}
|
|
if got := dst["GIT_CONFIG_KEY_0"]; got != "core.hooksPath" {
|
|
t.Fatalf("GIT_CONFIG_KEY_0 = %q, want inherited profile entry", got)
|
|
}
|
|
if got := dst["GIT_CONFIG_KEY_1"]; got != "credential.https://github.com.helper" {
|
|
t.Fatalf("GIT_CONFIG_KEY_1 = %q, want request entry", got)
|
|
}
|
|
}
|
|
|
|
func TestMergeEnvFillMissingComposesIndexedGitConfigMismatchedCount(t *testing.T) {
|
|
dst := map[string]string{
|
|
"GIT_CONFIG_COUNT": "1",
|
|
"GIT_CONFIG_KEY_0": "credential.https://github.com.helper",
|
|
"GIT_CONFIG_VALUE_0": "!agentctl git-credential",
|
|
}
|
|
src := map[string]string{
|
|
"GIT_CONFIG_COUNT": "2",
|
|
"GIT_CONFIG_KEY_0": "core.hooksPath",
|
|
"GIT_CONFIG_VALUE_0": "/opt/locstat/hooks",
|
|
"GIT_CONFIG_KEY_1": "core.autocrlf",
|
|
"GIT_CONFIG_VALUE_1": "input",
|
|
}
|
|
|
|
mergeEnvFillMissing(dst, src)
|
|
|
|
if got := dst["GIT_CONFIG_COUNT"]; got != "3" {
|
|
t.Fatalf("GIT_CONFIG_COUNT = %q, want 3", got)
|
|
}
|
|
if got := dst["GIT_CONFIG_KEY_0"]; got != "core.hooksPath" {
|
|
t.Fatalf("GIT_CONFIG_KEY_0 = %q, want inherited profile entry", got)
|
|
}
|
|
if got := dst["GIT_CONFIG_KEY_2"]; got != "credential.https://github.com.helper" {
|
|
t.Fatalf("GIT_CONFIG_KEY_2 = %q, want request entry", got)
|
|
}
|
|
}
|
|
|
|
func TestComposeExecutionRuntimeEnvironmentRemovesObsoleteManagedCredentials(t *testing.T) {
|
|
base := map[string]string{
|
|
githubauth.CredentialBrokerURLEnv: "https://broker.example/old",
|
|
githubauth.CredentialLeaseEnv: "old-lease",
|
|
"GIT_CONFIG_COUNT": "2",
|
|
"GIT_CONFIG_KEY_0": "core.hooksPath",
|
|
"GIT_CONFIG_VALUE_0": "/user/hooks",
|
|
"GIT_CONFIG_KEY_1": "credential.https://github.com.helper",
|
|
"GIT_CONFIG_VALUE_1": "!f() { : " + githubauth.HostGitHubCredentialHelperMarker + "; '/old/gh' auth git-credential \"$@\"; }; f",
|
|
}
|
|
|
|
got, err := composeExecutionRuntimeEnvironment(base, map[string]string{"CURRENT": "yes"})
|
|
if err != nil {
|
|
t.Fatalf("composeExecutionRuntimeEnvironment() error = %v", err)
|
|
}
|
|
if _, present := got[githubauth.CredentialBrokerURLEnv]; present {
|
|
t.Fatalf("obsolete broker URL remained: %#v", got)
|
|
}
|
|
if _, present := got[githubauth.CredentialLeaseEnv]; present {
|
|
t.Fatalf("obsolete credential lease remained: %#v", got)
|
|
}
|
|
if got["CURRENT"] != "yes" || got["GIT_CONFIG_COUNT"] != "1" ||
|
|
got["GIT_CONFIG_KEY_0"] != "core.hooksPath" || got["GIT_CONFIG_VALUE_0"] != "/user/hooks" {
|
|
t.Fatalf("composed runtime environment = %#v, want current value and inherited user config", got)
|
|
}
|
|
}
|
|
|
|
func TestResolveAgentProfileEnvVars_SecretAndValue(t *testing.T) {
|
|
store := newInMemorySecretStore()
|
|
_ = store.Create(context.Background(), &secrets.SecretWithValue{
|
|
Secret: secrets.Secret{ID: "sec-1", Name: "test"},
|
|
Value: "secret-value",
|
|
})
|
|
|
|
log, _ := logger.NewLogger(logger.LoggingConfig{Level: "error", Format: "json"})
|
|
m := &Manager{logger: log, secretStore: store}
|
|
resolved, err := m.resolveAgentProfileEnvVars(context.Background(), []settingsmodels.ProfileEnvVar{
|
|
{Key: "PLAIN", Value: "plain"},
|
|
{Key: "FROM_SECRET", SecretID: "sec-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("resolveAgentProfileEnvVars: %v", err)
|
|
}
|
|
if resolved["PLAIN"] != "plain" {
|
|
t.Fatalf("PLAIN: got %q", resolved["PLAIN"])
|
|
}
|
|
if resolved["FROM_SECRET"] != "secret-value" {
|
|
t.Fatalf("FROM_SECRET: got %q", resolved["FROM_SECRET"])
|
|
}
|
|
}
|
|
|
|
func TestResolveAgentProfileEnvVars_RejectsWorkspaceSecret(t *testing.T) {
|
|
store := newInMemorySecretStore()
|
|
if err := store.Create(context.Background(), &secrets.SecretWithValue{
|
|
Secret: secrets.Secret{ID: "workspace-secret", Name: "workspace", Scope: secrets.ScopeWorkspace, WorkspaceID: "workspace-1"},
|
|
Value: "must-not-resolve",
|
|
}); err != nil {
|
|
t.Fatalf("seed workspace secret: %v", err)
|
|
}
|
|
|
|
log, _ := logger.NewLogger(logger.LoggingConfig{Level: "error", Format: "json"})
|
|
m := &Manager{logger: log, secretStore: store}
|
|
resolved, err := m.resolveAgentProfileEnvVars(context.Background(), []settingsmodels.ProfileEnvVar{{
|
|
Key: "WORKSPACE_ONLY", SecretID: "workspace-secret",
|
|
}})
|
|
if err == nil {
|
|
t.Fatal("workspace secret resolved without an error")
|
|
}
|
|
if len(resolved) != 0 {
|
|
t.Fatalf("workspace secret was resolved into profile environment: %#v", resolved)
|
|
}
|
|
}
|
|
|
|
func TestResolveAgentProfileEnvVarsPreservesCancellation(t *testing.T) {
|
|
for name, cause := range map[string]error{
|
|
"canceled": context.Canceled,
|
|
"deadline exceeded": context.DeadlineExceeded,
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
store := newInMemorySecretStore()
|
|
store.revealErr = cause
|
|
_ = store.Create(context.Background(), &secrets.SecretWithValue{
|
|
Secret: secrets.Secret{ID: "sec-cancel", Name: "cancel"},
|
|
Value: "secret-value",
|
|
})
|
|
|
|
log, _ := logger.NewLogger(logger.LoggingConfig{Level: "error", Format: "json"})
|
|
m := &Manager{logger: log, secretStore: store}
|
|
resolved, err := m.resolveAgentProfileEnvVars(context.Background(), []settingsmodels.ProfileEnvVar{
|
|
{Key: "FROM_SECRET", SecretID: "sec-cancel"},
|
|
})
|
|
if !errors.Is(err, cause) {
|
|
t.Fatalf("error = %v, want %v", err, cause)
|
|
}
|
|
if errors.Is(err, ErrProfileSecretUnavailable) {
|
|
t.Fatalf("cancellation was sanitized as a profile secret error: %v", err)
|
|
}
|
|
if resolved != nil {
|
|
t.Fatalf("resolved = %#v, want nil", resolved)
|
|
}
|
|
})
|
|
}
|
|
}
|