8779c88d79
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
156 lines
5.1 KiB
Go
156 lines
5.1 KiB
Go
package backendapp
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/kandev/kandev/internal/agent/registry"
|
|
"github.com/kandev/kandev/internal/common/config"
|
|
"github.com/kandev/kandev/internal/events/bus"
|
|
"github.com/kandev/kandev/internal/persistence/requiredstores"
|
|
"github.com/kandev/kandev/internal/secrets"
|
|
"github.com/kandev/kandev/internal/startup"
|
|
)
|
|
|
|
// TestProvideServicesWiresPluginsKandevVersion pins the production wiring of
|
|
// the running build version into the plugin service.
|
|
//
|
|
// internal/plugins has always been able to enforce a package's
|
|
// manifest.min_kandev_version, but checkMinKandevVersion short-circuits to
|
|
// nil while s.kandevVersion is empty — so for as long as SetKandevVersion had
|
|
// no production caller, the whole mechanism was a silent no-op in every
|
|
// shipped build while its unit tests stayed green. Only a test that goes
|
|
// through provideServices can catch that regressing again.
|
|
func TestProvideServicesWiresPluginsKandevVersion(t *testing.T) {
|
|
const wantVersion = "9.8.7"
|
|
|
|
services, _, _ := provideTestServices(t, wantVersion)
|
|
if services.Plugins == nil {
|
|
t.Fatal("provideServices returned a nil Plugins service")
|
|
}
|
|
if got := services.Plugins.KandevVersion(); got != wantVersion {
|
|
t.Fatalf("Plugins.KandevVersion() = %q, want %q — min_kandev_version is unenforced without it", got, wantVersion)
|
|
}
|
|
}
|
|
|
|
func TestRequiredStoreBootstrapCompleteness(t *testing.T) {
|
|
_, _, repos := provideTestServices(t, "test-bootstrap-completeness")
|
|
|
|
// Repository and service construction cover every catalog entry except the
|
|
// three stores deliberately initialized in later startup phases.
|
|
wantPending := []string{"message-queue", "delivery", "storage"}
|
|
gotPending := repos.RequiredStores.UnavailableStoreIDs()
|
|
if !reflect.DeepEqual(gotPending, wantPending) {
|
|
t.Fatalf("pending required stores = %v, want %v", gotPending, wantPending)
|
|
}
|
|
}
|
|
|
|
func TestRequiredStoreFailure(t *testing.T) {
|
|
tracker, err := requiredstores.NewTracker([]requiredstores.Descriptor{{
|
|
ID: "task", OwnerPackage: "internal/task", RequiredTables: []string{"tasks"},
|
|
Sweep: startup.StepStoresRepositories,
|
|
}})
|
|
if err != nil {
|
|
t.Fatalf("NewTracker: %v", err)
|
|
}
|
|
constructorErr := errors.New("schema constructor failed")
|
|
got := recordRequiredStore(context.Background(), tracker, "task", constructorErr)
|
|
if !errors.Is(got, constructorErr) {
|
|
t.Fatalf("recordRequiredStore() error = %v, want %v", got, constructorErr)
|
|
}
|
|
if !strings.Contains(got.Error(), `required store "task"`) {
|
|
t.Fatalf("recordRequiredStore() error = %q, want store ID", got)
|
|
}
|
|
if err := tracker.ValidateComplete(); err == nil || !strings.Contains(err.Error(), "task") {
|
|
t.Fatalf("ValidateComplete() error = %v, want failed task store", err)
|
|
}
|
|
}
|
|
|
|
func TestExternalProviderFailureIsolation(t *testing.T) {
|
|
t.Setenv("KANDEV_MOCK_GITHUB", "")
|
|
t.Setenv("GH_TOKEN", "")
|
|
t.Setenv("GITHUB_TOKEN", "")
|
|
t.Setenv("PATH", t.TempDir())
|
|
|
|
pool := newMessageQueueSettingsTestPool(t)
|
|
log := newTestLogger()
|
|
externalErr := errors.New("credential backend unavailable")
|
|
service, cleanup, err := initGitHubServiceRequired(
|
|
&config.Config{},
|
|
pool,
|
|
bus.NewMemoryEventBus(log),
|
|
failingExternalSecretStore{err: externalErr},
|
|
log,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("external credential failure made local store fatal: %v", err)
|
|
}
|
|
if service == nil {
|
|
t.Fatal("GitHub service is nil after external credential failure")
|
|
}
|
|
if cleanup != nil {
|
|
t.Cleanup(func() { _ = cleanup() })
|
|
}
|
|
if _, err := service.GetWorkspaceSettings(context.Background(), "external-failure-workspace"); err != nil {
|
|
t.Fatalf("local GitHub store unavailable after credential failure: %v", err)
|
|
}
|
|
}
|
|
|
|
// provideTestServices boots the real service graph over a temp-dir SQLite
|
|
// database, the same way Run does, and returns the resulting services.
|
|
func provideTestServices(t *testing.T, version string) (*Services, *config.Config, *Repositories) {
|
|
t.Helper()
|
|
|
|
cfg := &config.Config{
|
|
HomeDir: t.TempDir(),
|
|
Database: config.DatabaseConfig{Driver: "sqlite"},
|
|
}
|
|
log := newTestLogger()
|
|
|
|
pool, repos, cleanups, err := provideRepositories(context.Background(), cfg, log, version)
|
|
if err != nil {
|
|
t.Fatalf("provideRepositories: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
for i := len(cleanups) - 1; i >= 0; i-- {
|
|
if cleanups[i] != nil {
|
|
_ = cleanups[i]()
|
|
}
|
|
}
|
|
})
|
|
|
|
agentRegistry, registryCleanup, err := registry.Provide(log)
|
|
if err != nil {
|
|
t.Fatalf("registry.Provide: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if registryCleanup != nil {
|
|
_ = registryCleanup()
|
|
}
|
|
})
|
|
|
|
services, _, err := provideServices(context.Background(), cfg, log, repos, pool, bus.NewMemoryEventBus(log), agentRegistry, version)
|
|
if err != nil {
|
|
t.Fatalf("provideServices: %v", err)
|
|
}
|
|
if services.PluginsCleanup != nil {
|
|
t.Cleanup(func() { _ = services.PluginsCleanup() })
|
|
}
|
|
if services.Workflow != nil {
|
|
t.Cleanup(func() { _ = services.Workflow.Close() })
|
|
}
|
|
return services, cfg, repos
|
|
}
|
|
|
|
type failingExternalSecretStore struct {
|
|
emptySecretStore
|
|
err error
|
|
}
|
|
|
|
func (s failingExternalSecretStore) List(context.Context) ([]*secrets.SecretListItem, error) {
|
|
return nil, s.err
|
|
}
|