Files
kandev/apps/backend/internal/backendapp/secret_references_integration_test.go
T
2026-09-09 07:50:57 +01:00

86 lines
3.9 KiB
Go

package backendapp
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
settingsmodels "github.com/kandev/kandev/internal/agent/settings/models"
settingsstore "github.com/kandev/kandev/internal/agent/settings/store"
"github.com/kandev/kandev/internal/common/logger"
"github.com/kandev/kandev/internal/secrets"
"github.com/kandev/kandev/internal/task/models"
ws "github.com/kandev/kandev/pkg/websocket"
)
// @covers AC-WORKSPACES-REPOSITORY-SECRETS-001.9
func TestSecretReferenceDeletionWithRealRepositories(t *testing.T) {
h := newBootStateTestHarness(t)
ctx := context.Background()
log, err := logger.NewLogger(logger.LoggingConfig{Level: "error", Format: "json"})
require.NoError(t, err)
agents, closeAgents, err := settingsstore.Provide(h.db, h.db, log)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, closeAgents()) })
crypto, err := secrets.NewMasterKeyProvider(t.TempDir())
require.NoError(t, err)
store, closeSecrets, err := secrets.Provide(h.db, h.db, crypto)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, closeSecrets()) })
svc := secrets.NewService(secrets.NewUserVisibleStore(store), log)
svc.SetReferenceChecker(secretReferenceChecker{agents: agents, tasks: h.taskRepo, authorizeWorkspace: h.taskSvc.AuthorizeWorkspaceAccess}.list)
secret, err := svc.Create(ctx, &secrets.CreateSecretRequest{Name: "MY_TOKEN", Value: "private-value"})
require.NoError(t, err)
seedSecretReferences(t, h, agents, secret.ID)
router := gin.New()
secrets.RegisterRoutes(router, ws.NewDispatcher(), svc, log)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, httptest.NewRequest(http.MethodDelete, "/api/v1/secrets/"+secret.ID, nil))
require.Equal(t, http.StatusConflict, rec.Code, rec.Body.String())
var response struct {
References []secrets.Reference `json:"references"`
}
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &response))
require.Len(t, response.References, 3)
value, err := svc.Reveal(ctx, secret.ID)
require.NoError(t, err)
require.Equal(t, "private-value", value)
rec = httptest.NewRecorder()
router.ServeHTTP(rec, httptest.NewRequest(http.MethodDelete, "/api/v1/secrets/"+secret.ID+"?force=true", nil))
require.Equal(t, http.StatusNoContent, rec.Code)
_, err = svc.Get(ctx, secret.ID)
require.ErrorIs(t, err, secrets.ErrNotFound)
refs, err := (secretReferenceChecker{agents: agents, tasks: h.taskRepo, authorizeWorkspace: h.taskSvc.AuthorizeWorkspaceAccess}).list(ctx, secret.ID)
require.NoError(t, err)
require.Len(t, refs, 3, "forced deletion must retain bindings for repair")
}
func seedSecretReferences(t *testing.T, h bootStateTestHarness, agents settingsstore.Repository, secretID string) {
t.Helper()
ctx := context.Background()
agent := &settingsmodels.Agent{Name: "secret-reference-test"}
require.NoError(t, agents.CreateAgent(ctx, agent))
require.NoError(t, agents.CreateAgentProfile(ctx, &settingsmodels.AgentProfile{
ID: "secret-agent-profile", AgentID: agent.ID, Name: "Claude review", Enabled: false,
EnvVars: []settingsmodels.ProfileEnvVar{{Key: "MY_TOKEN", SecretID: secretID}},
}))
require.NoError(t, h.taskRepo.CreateExecutor(ctx, &models.Executor{
ID: "secret-executor", Name: "Secret executor", Type: models.ExecutorTypeLocal, Status: models.ExecutorStatusActive,
}))
require.NoError(t, h.taskRepo.CreateExecutorProfile(ctx, &models.ExecutorProfile{
ID: "secret-executor-profile", ExecutorID: "secret-executor", Name: "Local",
EnvVars: []models.ProfileEnvVar{{Key: "EXEC_TOKEN", SecretID: secretID}},
}))
workspace := &models.Workspace{ID: "secret-workspace", Name: "Secret workspace"}
require.NoError(t, h.taskRepo.CreateWorkspace(ctx, workspace))
require.NoError(t, h.taskRepo.CreateRepositoryWithSecretBindings(ctx, &models.Repository{
ID: "secret-repository", WorkspaceID: workspace.ID, Name: "App", SourceType: "local",
}, []models.RepositorySecretBinding{{Key: "REPO_TOKEN", SecretID: secretID}}))
}