- Add Workspace domain (entity, repository, service, handler, DTO) - Add multi-tenant K8s client with tenant binding and quota management - Add K8s diagnostics client (instance diagnostics) - Add authorization middleware (authz package) - Restructure frontend to feature-based architecture (features/) - Add User Management page in configuration - Add AccessDenied page and route guards - Refactor shared components (form inputs, layout, UI) - Update Tailwind config for new design system - Add comprehensive documentation (docs/, tasks/, plans) - Improve cluster service with better kubeconfig handling - Add tests for crypto, config, helm client, tenant binding
172 lines
5.3 KiB
Go
172 lines
5.3 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
persistencemock "github.com/ocdp/cluster-service/internal/adapter/output/persistence/mock"
|
|
"github.com/ocdp/cluster-service/internal/domain/entity"
|
|
"github.com/ocdp/cluster-service/internal/domain/repository"
|
|
"github.com/ocdp/cluster-service/internal/pkg/authz"
|
|
)
|
|
|
|
func TestDeleteInstanceIgnoresMissingRelease(t *testing.T) {
|
|
principal := &authz.Principal{UserID: "user-1", Username: "tester", Role: authz.RoleUser, WorkspaceID: entity.DefaultWorkspaceID}
|
|
ctx := authz.WithPrincipal(context.Background(), principal)
|
|
instanceRepo := persistencemock.NewInstanceRepositoryMock()
|
|
|
|
instance := &entity.Instance{
|
|
ID: "inst-1",
|
|
WorkspaceID: entity.DefaultWorkspaceID,
|
|
OwnerID: "user-1",
|
|
ClusterID: "cluster-1",
|
|
Name: "demo",
|
|
Namespace: "default",
|
|
}
|
|
if err := instanceRepo.Create(ctx, instance); err != nil {
|
|
t.Fatalf("failed to seed instance: %v", err)
|
|
}
|
|
|
|
cluster := &entity.Cluster{ID: "cluster-1", Name: "cluster", Host: "https://example.com"}
|
|
clusterRepo := &stubClusterRepo{cluster: cluster}
|
|
|
|
svc := NewInstanceService(
|
|
instanceRepo,
|
|
clusterRepo,
|
|
nil,
|
|
&stubHelmClient{uninstallErr: entity.ErrInstanceNotFound},
|
|
nil,
|
|
nil,
|
|
)
|
|
|
|
if err := svc.DeleteInstance(ctx, instance.ID); err != nil {
|
|
t.Fatalf("DeleteInstance returned error: %v", err)
|
|
}
|
|
|
|
waitForInstanceDeleted(t, ctx, instanceRepo, instance.ID)
|
|
}
|
|
|
|
func TestEnforceNamespaceValuesOverridesChartNamespaceKnobs(t *testing.T) {
|
|
instance := &entity.Instance{
|
|
Namespace: "ocdp-u-alice",
|
|
Values: map[string]interface{}{
|
|
"namespace": "default",
|
|
"namespaceOverride": "default",
|
|
"targetNamespace": "default",
|
|
"global": map[string]interface{}{
|
|
"namespace": "default",
|
|
"namespaceOverride": "default",
|
|
},
|
|
"image": map[string]interface{}{
|
|
"repository": "nginx",
|
|
},
|
|
},
|
|
}
|
|
|
|
enforceNamespaceValues(instance)
|
|
|
|
if instance.Values["namespace"] != "ocdp-u-alice" {
|
|
t.Fatalf("expected top-level namespace to be enforced, got %#v", instance.Values["namespace"])
|
|
}
|
|
if instance.Values["namespaceOverride"] != "ocdp-u-alice" {
|
|
t.Fatalf("expected namespaceOverride to be enforced, got %#v", instance.Values["namespaceOverride"])
|
|
}
|
|
if instance.Values["targetNamespace"] != "ocdp-u-alice" {
|
|
t.Fatalf("expected targetNamespace to be enforced, got %#v", instance.Values["targetNamespace"])
|
|
}
|
|
global, ok := instance.Values["global"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("expected global map, got %#v", instance.Values["global"])
|
|
}
|
|
if global["namespace"] != "ocdp-u-alice" || global["namespaceOverride"] != "ocdp-u-alice" {
|
|
t.Fatalf("expected global namespace keys to be enforced, got %#v", global)
|
|
}
|
|
}
|
|
|
|
func waitForInstanceDeleted(t *testing.T, ctx context.Context, repo repository.InstanceRepository, id string) {
|
|
t.Helper()
|
|
|
|
deadline := time.After(2 * time.Second)
|
|
ticker := time.NewTicker(10 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-deadline:
|
|
_, err := repo.GetByID(ctx, id)
|
|
t.Fatalf("expected instance removed, got err=%v", err)
|
|
case <-ticker.C:
|
|
if _, err := repo.GetByID(ctx, id); errors.Is(err, entity.ErrInstanceNotFound) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type stubClusterRepo struct {
|
|
cluster *entity.Cluster
|
|
}
|
|
|
|
func (s *stubClusterRepo) Create(ctx context.Context, cluster *entity.Cluster) error {
|
|
s.cluster = cluster
|
|
return nil
|
|
}
|
|
|
|
func (s *stubClusterRepo) GetByID(ctx context.Context, id string) (*entity.Cluster, error) {
|
|
if s.cluster != nil && s.cluster.ID == id {
|
|
return s.cluster, nil
|
|
}
|
|
return nil, entity.ErrClusterNotFound
|
|
}
|
|
|
|
func (*stubClusterRepo) GetByName(ctx context.Context, name string) (*entity.Cluster, error) {
|
|
return nil, entity.ErrClusterNotFound
|
|
}
|
|
|
|
func (*stubClusterRepo) Update(ctx context.Context, cluster *entity.Cluster) error { return nil }
|
|
|
|
func (*stubClusterRepo) Delete(ctx context.Context, id string) error { return nil }
|
|
|
|
func (*stubClusterRepo) List(ctx context.Context) ([]*entity.Cluster, error) { return nil, nil }
|
|
|
|
type stubHelmClient struct {
|
|
uninstallErr error
|
|
}
|
|
|
|
func (*stubHelmClient) Install(ctx context.Context, cluster *entity.Cluster, instance *entity.Instance) error {
|
|
return nil
|
|
}
|
|
|
|
func (*stubHelmClient) Upgrade(ctx context.Context, cluster *entity.Cluster, instance *entity.Instance) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *stubHelmClient) Uninstall(ctx context.Context, cluster *entity.Cluster, releaseName, namespace string) error {
|
|
return s.uninstallErr
|
|
}
|
|
|
|
func (*stubHelmClient) Rollback(ctx context.Context, cluster *entity.Cluster, releaseName, namespace string, revision int) error {
|
|
return nil
|
|
}
|
|
|
|
func (*stubHelmClient) GetStatus(ctx context.Context, cluster *entity.Cluster, releaseName, namespace string) (*entity.Instance, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (*stubHelmClient) GetHistory(ctx context.Context, cluster *entity.Cluster, releaseName, namespace string) ([]*entity.ReleaseHistory, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (*stubHelmClient) List(ctx context.Context, cluster *entity.Cluster, namespace string) ([]*entity.Instance, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (*stubHelmClient) GetValues(ctx context.Context, cluster *entity.Cluster, releaseName, namespace string) (map[string]interface{}, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
var _ repository.ClusterRepository = (*stubClusterRepo)(nil)
|
|
var _ repository.HelmClient = (*stubHelmClient)(nil)
|