Skip to content

Commit

Permalink
#81 map error types in globalConfigRepository
Browse files Browse the repository at this point in the history
Also add tests and go doc
  • Loading branch information
alexander-dammeier committed Oct 14, 2024
1 parent 27e8c38 commit 11c590c
Show file tree
Hide file tree
Showing 6 changed files with 376 additions and 15 deletions.
39 changes: 25 additions & 14 deletions pkg/adapter/config/kubernetes/globalConfigRepository.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,45 @@ import (
"context"
"github.com/cloudogu/k8s-blueprint-operator/pkg/domainservice"
"github.com/cloudogu/k8s-registry-lib/config"
"github.com/cloudogu/k8s-registry-lib/repository"
liberrors "github.com/cloudogu/k8s-registry-lib/errors"
)

type GlobalConfigRepository struct {
repo repository.GlobalConfigRepository
repo k8sGlobalConfigRepo
}

func NewGlobalConfigRepository(repo repository.GlobalConfigRepository) *GlobalConfigRepository {
func NewGlobalConfigRepository(repo k8sGlobalConfigRepo) *GlobalConfigRepository {
return &GlobalConfigRepository{repo: repo}
}

func (e GlobalConfigRepository) Get(ctx context.Context) (config.GlobalConfig, error) {
return e.repo.Get(ctx)
//TODO: add own error types again
//if registry.IsKeyNotFoundError(err) {
// return nil, domainservice.NewNotFoundError(err, "could not find key %q from global config in etcd", key)
//} else if err != nil {
// return nil, domainservice.NewInternalError(err, "failed to get value for key %q from global config in etcd", key)
//}
loadedConfig, err := e.repo.Get(ctx)
if err != nil {
if liberrors.IsNotFoundError(err) {
return loadedConfig, domainservice.NewNotFoundError(err, "could not find global config. Check if your ecosystem is ready for operation")
} else if liberrors.IsConnectionError(err) {
return loadedConfig, domainservice.NewInternalError(err, "could not load global config due to connection problems")
} else {
// GenericError and fallback if even that would not match the error
return loadedConfig, domainservice.NewInternalError(err, "could not load global config due to an unknown problem")
}
}
return loadedConfig, nil
}

func (e GlobalConfigRepository) Update(ctx context.Context, config config.GlobalConfig) (config.GlobalConfig, error) {
updatedConfig, err := e.repo.Update(ctx, config)
// TODO: we cannot see here, if there is a real conflict or there was a connection error.
// With a conflict, we can immediately restart the business process
// With an connection error we need a longer backoff (internalError)
if err != nil {
return config, domainservice.NewInternalError(err, "failed to update global config")
if liberrors.IsNotFoundError(err) {
return updatedConfig, domainservice.NewNotFoundError(err, "could not update global config. Check if your ecosystem is ready for operation")
} else if liberrors.IsConnectionError(err) {
return updatedConfig, domainservice.NewInternalError(err, "could not update global config due to connection problems")
} else if liberrors.IsConflictError(err) {
return updatedConfig, domainservice.NewInternalError(err, "could not update global config due to conflicting changes")
} else {
// GenericError and fallback if even that would not match the error
return updatedConfig, domainservice.NewInternalError(err, "could not update global config due to an unknown problem")
}
}
return updatedConfig, nil
}
151 changes: 151 additions & 0 deletions pkg/adapter/config/kubernetes/globalConfigRepository_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package kubernetes

import (
"fmt"
"github.com/cloudogu/k8s-blueprint-operator/pkg/domainservice"
"github.com/cloudogu/k8s-registry-lib/config"
"github.com/cloudogu/k8s-registry-lib/errors"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
"testing"
)

var testCtx = context.TODO()

// testGlobalConfig is used as immutable structure for tests
var testGlobalConfig = config.CreateGlobalConfig(map[config.Key]config.Value{
"fqdn": "domain.local",
})

func TestNewGlobalConfigRepository(t *testing.T) {
repoMock := newMockK8sGlobalConfigRepo(t)
//given
repo := NewGlobalConfigRepository(repoMock)
//when
assert.Equal(t, repoMock, repo.repo)
}

func TestGlobalConfigRepository_Get(t *testing.T) {

t.Run("get", func(t *testing.T) {
repoMock := newMockK8sGlobalConfigRepo(t)
//given
repoMock.EXPECT().Get(testCtx).Return(testGlobalConfig, nil)
repo := NewGlobalConfigRepository(repoMock)
//when
actualConfig, err := repo.Get(testCtx)
//then
assert.NoError(t, err)
assert.Equal(t, testGlobalConfig, actualConfig)
})
t.Run("global config not found", func(t *testing.T) {
repoMock := newMockK8sGlobalConfigRepo(t)
//given
// wrap the error because the original implementation does it too
givenError := fmt.Errorf("wrapping error: %w", errors.NewNotFoundError(assert.AnError))
repoMock.EXPECT().Get(testCtx).Return(testGlobalConfig, givenError)
repo := NewGlobalConfigRepository(repoMock)
//when
_, err := repo.Get(testCtx)
//then
assert.ErrorContains(t, err, givenError.Error())
assert.True(t, domainservice.IsNotFoundError(err), "error is no NotFoundError")
})
t.Run("internal error if connection error happens", func(t *testing.T) {
repoMock := newMockK8sGlobalConfigRepo(t)
//given
// wrap the error because the original implementation does it too
givenError := fmt.Errorf("wrapping error: %w", errors.NewConnectionError(assert.AnError))
repoMock.EXPECT().Get(testCtx).Return(testGlobalConfig, givenError)
repo := NewGlobalConfigRepository(repoMock)
//when
_, err := repo.Get(testCtx)
//then
assert.ErrorContains(t, err, givenError.Error())
assert.ErrorContains(t, err, "could not load global config due to connection problems")
assert.True(t, domainservice.IsInternalError(err), "error is no InternalError")
})
t.Run("internal error on all other errors", func(t *testing.T) {
repoMock := newMockK8sGlobalConfigRepo(t)
//given
// wrap the error because the original implementation does it too
givenError := fmt.Errorf("wrapping error: %w", errors.NewGenericError(assert.AnError))
repoMock.EXPECT().Get(testCtx).Return(testGlobalConfig, givenError)
repo := NewGlobalConfigRepository(repoMock)
//when
_, err := repo.Get(testCtx)
//then
assert.ErrorContains(t, err, givenError.Error())
assert.ErrorContains(t, err, "could not load global config due to an unknown problem")
assert.True(t, domainservice.IsInternalError(err), "error is no InternalError")
})
}

func TestGlobalConfigRepository_Update(t *testing.T) {
t.Run("update global config", func(t *testing.T) {
repoMock := newMockK8sGlobalConfigRepo(t)
//given
repoMock.EXPECT().Update(testCtx, testGlobalConfig).Return(testGlobalConfig, nil)
repo := NewGlobalConfigRepository(repoMock)
//when
actualConfig, err := repo.Update(testCtx, testGlobalConfig)
//then
assert.NoError(t, err)
assert.Equal(t, testGlobalConfig, actualConfig)
})
t.Run("global config not found", func(t *testing.T) {
repoMock := newMockK8sGlobalConfigRepo(t)
//given
// wrap the error because the original implementation does it too
givenError := fmt.Errorf("wrapping error: %w", errors.NewNotFoundError(assert.AnError))
repoMock.EXPECT().Update(testCtx, testGlobalConfig).Return(testGlobalConfig, givenError)
repo := NewGlobalConfigRepository(repoMock)
//when
_, err := repo.Update(testCtx, testGlobalConfig)
//then
assert.ErrorContains(t, err, givenError.Error())
assert.True(t, domainservice.IsNotFoundError(err), "error is no NotFoundError")
})
t.Run("conflicts while updating global config", func(t *testing.T) {
repoMock := newMockK8sGlobalConfigRepo(t)
//given
// wrap the error because the original implementation does it too
givenError := fmt.Errorf("wrapping error: %w", errors.NewConflictError(assert.AnError))
repoMock.EXPECT().Update(testCtx, testGlobalConfig).Return(testGlobalConfig, givenError)
repo := NewGlobalConfigRepository(repoMock)
//when
_, err := repo.Update(testCtx, testGlobalConfig)
//then
assert.ErrorContains(t, err, givenError.Error())
assert.ErrorContains(t, err, "could not update global config due to conflicting changes")
assert.True(t, domainservice.IsConflictError(err), "error is no ConflictError")
})
t.Run("internal error if connection error happens", func(t *testing.T) {
repoMock := newMockK8sGlobalConfigRepo(t)
//given
// wrap the error because the original implementation does it too
givenError := fmt.Errorf("wrapping error: %w", errors.NewConnectionError(assert.AnError))
repoMock.EXPECT().Update(testCtx, testGlobalConfig).Return(testGlobalConfig, givenError)
repo := NewGlobalConfigRepository(repoMock)
//when
_, err := repo.Update(testCtx, testGlobalConfig)
//then
assert.ErrorContains(t, err, givenError.Error())
assert.ErrorContains(t, err, "could not update global config due to connection problems")
assert.True(t, domainservice.IsInternalError(err), "error is no InternalError")
})
t.Run("internal error on all other errors", func(t *testing.T) {
repoMock := newMockK8sGlobalConfigRepo(t)
//given
// wrap the error because the original implementation does it too
givenError := fmt.Errorf("wrapping error: %w", errors.NewGenericError(assert.AnError))
repoMock.EXPECT().Update(testCtx, testGlobalConfig).Return(testGlobalConfig, givenError)
repo := NewGlobalConfigRepository(repoMock)
//when
_, err := repo.Update(testCtx, testGlobalConfig)
//then
assert.ErrorContains(t, err, givenError.Error())
assert.ErrorContains(t, err, "could not update global config due to an unknown problem")
assert.True(t, domainservice.IsInternalError(err), "error is no InternalError")
})
}
11 changes: 11 additions & 0 deletions pkg/adapter/config/kubernetes/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package kubernetes

import (
"context"
"github.com/cloudogu/k8s-registry-lib/config"
)

type k8sGlobalConfigRepo interface {
Get(ctx context.Context) (config.GlobalConfig, error)
Update(ctx context.Context, globalConfig config.GlobalConfig) (config.GlobalConfig, error)
}
151 changes: 151 additions & 0 deletions pkg/adapter/config/kubernetes/mock_k8sGlobalConfigRepo_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 11c590c

Please sign in to comment.