test(soteria): cover auth and backup handlers
This commit is contained in:
parent
783c9f7d1a
commit
9a061c14ae
126
internal/server/auth_support_test.go
Normal file
126
internal/server/auth_support_test.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"scm.bstein.dev/bstein/soteria/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthorizeCoversAuthModesAndGroupChecks(t *testing.T) {
|
||||||
|
t.Run("auth disabled", func(t *testing.T) {
|
||||||
|
srv := &Server{cfg: &config.Config{AuthRequired: false}}
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
||||||
|
identity, status, err := srv.authorize(req)
|
||||||
|
if err != nil || status != http.StatusOK || identity.Authenticated {
|
||||||
|
t.Fatalf("expected auth-disabled request to pass anonymously, got identity=%#v status=%d err=%v", identity, status, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bearer token", func(t *testing.T) {
|
||||||
|
srv := &Server{cfg: &config.Config{
|
||||||
|
AuthRequired: true,
|
||||||
|
AuthBearerTokens: []string{"atlas-secret"},
|
||||||
|
}}
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer atlas-secret")
|
||||||
|
identity, status, err := srv.authorize(req)
|
||||||
|
if err != nil || status != http.StatusOK || identity.User != "service-token" {
|
||||||
|
t.Fatalf("expected bearer token auth, got identity=%#v status=%d err=%v", identity, status, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("forwarded headers without group restriction", func(t *testing.T) {
|
||||||
|
srv := &Server{cfg: &config.Config{AuthRequired: true}}
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
||||||
|
req.Header.Set("X-Forwarded-User", "brad")
|
||||||
|
req.Header.Set("X-Forwarded-Email", "brad@bstein.dev")
|
||||||
|
req.Header.Set("X-Forwarded-Groups", "/ops;/dev")
|
||||||
|
identity, status, err := srv.authorize(req)
|
||||||
|
if err != nil || status != http.StatusOK || identity.User != "brad" || len(identity.Groups) != 2 {
|
||||||
|
t.Fatalf("expected forwarded header auth, got identity=%#v status=%d err=%v", identity, status, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing identity", func(t *testing.T) {
|
||||||
|
srv := &Server{cfg: &config.Config{AuthRequired: true}}
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
||||||
|
_, status, err := srv.authorize(req)
|
||||||
|
if err == nil || status != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("expected unauthorized missing identity, got status=%d err=%v", status, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("group allowed", func(t *testing.T) {
|
||||||
|
srv := &Server{cfg: &config.Config{
|
||||||
|
AuthRequired: true,
|
||||||
|
AllowedGroups: []string{"admin", "ops"},
|
||||||
|
}}
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
||||||
|
req.Header.Set("X-Auth-Request-User", "brad")
|
||||||
|
req.Header.Set("X-Auth-Request-Groups", "/dev,/ops")
|
||||||
|
identity, status, err := srv.authorize(req)
|
||||||
|
if err != nil || status != http.StatusOK || identity.User != "brad" {
|
||||||
|
t.Fatalf("expected allowed group auth, got identity=%#v status=%d err=%v", identity, status, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("group forbidden", func(t *testing.T) {
|
||||||
|
srv := &Server{cfg: &config.Config{
|
||||||
|
AuthRequired: true,
|
||||||
|
AllowedGroups: []string{"admin"},
|
||||||
|
}}
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
||||||
|
req.Header.Set("X-Forwarded-User", "brad")
|
||||||
|
req.Header.Set("X-Forwarded-Groups", "/dev")
|
||||||
|
_, status, err := srv.authorize(req)
|
||||||
|
if err == nil || status != http.StatusForbidden {
|
||||||
|
t.Fatalf("expected forbidden group failure, got status=%d err=%v", status, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequesterAndAuthzHelpers(t *testing.T) {
|
||||||
|
ctx := context.WithValue(context.Background(), authContextKey, authIdentity{
|
||||||
|
Authenticated: true,
|
||||||
|
User: "brad",
|
||||||
|
Email: "brad@bstein.dev",
|
||||||
|
})
|
||||||
|
if got := currentRequester(ctx); got != "brad" {
|
||||||
|
t.Fatalf("expected requester user brad, got %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.WithValue(context.Background(), authContextKey, authIdentity{
|
||||||
|
Authenticated: true,
|
||||||
|
Email: "brad@bstein.dev",
|
||||||
|
})
|
||||||
|
if got := currentRequester(ctx); got != "brad@bstein.dev" {
|
||||||
|
t.Fatalf("expected requester email, got %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.WithValue(context.Background(), authContextKey, authIdentity{Authenticated: true})
|
||||||
|
if got := currentRequester(ctx); got != "authenticated" {
|
||||||
|
t.Fatalf("expected generic authenticated requester, got %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := currentRequester(context.Background()); got != "anonymous" {
|
||||||
|
t.Fatalf("expected anonymous requester, got %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := authzReason(http.StatusUnauthorized, errSentinel); got != "unauthenticated" {
|
||||||
|
t.Fatalf("expected unauthenticated reason, got %q", got)
|
||||||
|
}
|
||||||
|
if got := authzReason(http.StatusForbidden, errSentinel); got != "forbidden_group" {
|
||||||
|
t.Fatalf("expected forbidden reason, got %q", got)
|
||||||
|
}
|
||||||
|
if got := authzReason(http.StatusBadGateway, errSentinel); got != "error" {
|
||||||
|
t.Fatalf("expected generic error reason, got %q", got)
|
||||||
|
}
|
||||||
|
if got := authzReason(http.StatusOK, nil); got != "unknown" {
|
||||||
|
t.Fatalf("expected unknown nil-error reason, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errSentinel = http.ErrAbortHandler
|
||||||
376
internal/server/backup_handlers_test.go
Normal file
376
internal/server/backup_handlers_test.go
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"scm.bstein.dev/bstein/soteria/internal/api"
|
||||||
|
"scm.bstein.dev/bstein/soteria/internal/config"
|
||||||
|
"scm.bstein.dev/bstein/soteria/internal/k8s"
|
||||||
|
"scm.bstein.dev/bstein/soteria/internal/longhorn"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertErrorResponseContains(t *testing.T, res *httptest.ResponseRecorder, wantStatus int, want string) {
|
||||||
|
t.Helper()
|
||||||
|
if res.Code != wantStatus {
|
||||||
|
t.Fatalf("expected %d, got %d: %s", wantStatus, res.Code, res.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload map[string]string
|
||||||
|
if err := json.Unmarshal(res.Body.Bytes(), &payload); err != nil {
|
||||||
|
t.Fatalf("decode error response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := payload["error"]; !strings.Contains(got, want) {
|
||||||
|
t.Fatalf("expected error containing %q, got %q", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type backupTestKubeClient struct {
|
||||||
|
*restoreTestKubeClient
|
||||||
|
createBackupErr error
|
||||||
|
listBackupJobsPVCErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *backupTestKubeClient) CreateBackupJob(ctx context.Context, cfg *config.Config, req api.BackupRequest) (string, string, error) {
|
||||||
|
if k.createBackupErr != nil {
|
||||||
|
return "", "", k.createBackupErr
|
||||||
|
}
|
||||||
|
return k.restoreTestKubeClient.fakeKubeClient.CreateBackupJob(ctx, cfg, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *backupTestKubeClient) ListBackupJobsForPVC(ctx context.Context, namespace, pvc string) ([]k8s.BackupJobSummary, error) {
|
||||||
|
if k.listBackupJobsPVCErr != nil {
|
||||||
|
return nil, k.listBackupJobsPVCErr
|
||||||
|
}
|
||||||
|
return k.restoreTestKubeClient.fakeKubeClient.ListBackupJobsForPVC(ctx, namespace, pvc)
|
||||||
|
}
|
||||||
|
|
||||||
|
type backupTestLonghornClient struct {
|
||||||
|
*restoreTestLonghornClient
|
||||||
|
listBackups []longhorn.Backup
|
||||||
|
listBackupsErr error
|
||||||
|
createSnapErr error
|
||||||
|
snapshotErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *backupTestLonghornClient) ListBackups(ctx context.Context, volumeName string) ([]longhorn.Backup, error) {
|
||||||
|
if l.listBackupsErr != nil {
|
||||||
|
return nil, l.listBackupsErr
|
||||||
|
}
|
||||||
|
if l.listBackups != nil {
|
||||||
|
return l.listBackups, nil
|
||||||
|
}
|
||||||
|
return l.restoreTestLonghornClient.fakeLonghornClient.ListBackups(ctx, volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *backupTestLonghornClient) CreateSnapshot(ctx context.Context, volume, name string, labels map[string]string) error {
|
||||||
|
if l.createSnapErr != nil {
|
||||||
|
return l.createSnapErr
|
||||||
|
}
|
||||||
|
return l.restoreTestLonghornClient.fakeLonghornClient.CreateSnapshot(ctx, volume, name, labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *backupTestLonghornClient) SnapshotBackup(ctx context.Context, volume, name string, labels map[string]string, backupMode string) (*longhorn.Volume, error) {
|
||||||
|
if l.snapshotErr != nil {
|
||||||
|
return nil, l.snapshotErr
|
||||||
|
}
|
||||||
|
return l.restoreTestLonghornClient.fakeLonghornClient.SnapshotBackup(ctx, volume, name, labels, backupMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBackupTestServer(cfg *config.Config, client kubeClient, longhornClient longhornClient) *Server {
|
||||||
|
return newRestoreTestServer(cfg, client, longhornClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleInventoryRejectsUnsupportedMethodAndBackendError(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{
|
||||||
|
fakeKubeClient: &fakeKubeClient{},
|
||||||
|
listPVCsErr: errors.New("inventory exploded"),
|
||||||
|
}},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/v1/inventory", nil)
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
srv.Handler().ServeHTTP(res, req)
|
||||||
|
if res.Code != http.StatusMethodNotAllowed {
|
||||||
|
t.Fatalf("expected 405, got %d: %s", res.Code, res.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
||||||
|
res = httptest.NewRecorder()
|
||||||
|
srv.Handler().ServeHTTP(res, req)
|
||||||
|
assertErrorResponseContains(t, res, http.StatusBadGateway, "inventory exploded")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleBackupsRejectsInvalidRequestsAndBackendErrors(t *testing.T) {
|
||||||
|
t.Run("invalid requests", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{
|
||||||
|
fakeKubeClient: &fakeKubeClient{},
|
||||||
|
}},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/v1/backups", nil)
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
srv.Handler().ServeHTTP(res, req)
|
||||||
|
if res.Code != http.StatusMethodNotAllowed {
|
||||||
|
t.Fatalf("expected 405, got %d: %s", res.Code, res.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/v1/backups", nil)
|
||||||
|
res = httptest.NewRecorder()
|
||||||
|
srv.Handler().ServeHTTP(res, req)
|
||||||
|
assertErrorResponseContains(t, res, http.StatusBadRequest, "namespace and pvc are required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("resolve pvc error", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{
|
||||||
|
fakeKubeClient: &fakeKubeClient{},
|
||||||
|
resolveErr: errors.New("resolve backup pvc exploded"),
|
||||||
|
}},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/backups?namespace=apps&pvc=data", nil)
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
srv.Handler().ServeHTTP(res, req)
|
||||||
|
assertErrorResponseContains(t, res, http.StatusBadRequest, "resolve backup pvc exploded")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("longhorn backend error", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{fakeKubeClient: &fakeKubeClient{}}},
|
||||||
|
&backupTestLonghornClient{
|
||||||
|
restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}},
|
||||||
|
listBackupsErr: errors.New("list backups exploded"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/backups?namespace=apps&pvc=data", nil)
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
srv.Handler().ServeHTTP(res, req)
|
||||||
|
assertErrorResponseContains(t, res, http.StatusBadGateway, "list backups exploded")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("restic backend error", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "restic"},
|
||||||
|
&backupTestKubeClient{
|
||||||
|
restoreTestKubeClient: &restoreTestKubeClient{fakeKubeClient: &fakeKubeClient{}},
|
||||||
|
listBackupJobsPVCErr: errors.New("list jobs exploded"),
|
||||||
|
},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/backups?namespace=apps&pvc=data", nil)
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
srv.Handler().ServeHTTP(res, req)
|
||||||
|
assertErrorResponseContains(t, res, http.StatusBadGateway, "list jobs exploded")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unsupported driver", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "mystery"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{fakeKubeClient: &fakeKubeClient{}}},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/v1/backups?namespace=apps&pvc=data", nil)
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
srv.Handler().ServeHTTP(res, req)
|
||||||
|
assertErrorResponseContains(t, res, http.StatusBadRequest, "unsupported backup driver")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleBackupAndNamespaceBackupValidationPaths(t *testing.T) {
|
||||||
|
t.Run("backup request validation", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "restic"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{fakeKubeClient: &fakeKubeClient{}}},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
body string
|
||||||
|
code int
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "method", body: "", code: http.StatusMethodNotAllowed, want: "method not allowed"},
|
||||||
|
{name: "invalid json", body: `{"namespace":`, code: http.StatusBadRequest, want: "invalid JSON"},
|
||||||
|
{name: "missing namespace", body: `{"pvc":"data"}`, code: http.StatusBadRequest, want: "namespace and pvc are required"},
|
||||||
|
{name: "invalid keep last", body: `{"namespace":"apps","pvc":"data","keep_last":-1}`, code: http.StatusBadRequest, want: "keep_last must be >="},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
method := http.MethodPost
|
||||||
|
if tc.name == "method" {
|
||||||
|
method = http.MethodGet
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(method, "/v1/backup", strings.NewReader(tc.body))
|
||||||
|
if method == http.MethodPost {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
srv.Handler().ServeHTTP(res, req)
|
||||||
|
assertErrorResponseContains(t, res, tc.code, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("namespace backup validation and backend", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "restic"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{
|
||||||
|
fakeKubeClient: &fakeKubeClient{},
|
||||||
|
listPVCsErr: errors.New("list namespace pvc exploded"),
|
||||||
|
}},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
body string
|
||||||
|
code int
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "invalid json", body: `{"namespace":`, code: http.StatusBadRequest, want: "invalid JSON"},
|
||||||
|
{name: "missing namespace", body: `{}`, code: http.StatusBadRequest, want: "namespace is required"},
|
||||||
|
{name: "invalid keep last", body: `{"namespace":"apps","keep_last":-1}`, code: http.StatusBadRequest, want: "keep_last must be >="},
|
||||||
|
{name: "invalid namespace", body: `{"namespace":"Bad_Ns"}`, code: http.StatusBadRequest, want: "namespace must be a valid Kubernetes DNS-1123 label"},
|
||||||
|
{name: "backend error", body: `{"namespace":"apps"}`, code: http.StatusBadGateway, want: "list namespace pvc exploded"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/v1/backup/namespace", strings.NewReader(tc.body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
srv.Handler().ServeHTTP(res, req)
|
||||||
|
assertErrorResponseContains(t, res, tc.code, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecuteBackupAndStatusHelpers(t *testing.T) {
|
||||||
|
t.Run("validation error", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{fakeKubeClient: &fakeKubeClient{}}},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
_, result, err := srv.executeBackup(context.Background(), api.BackupRequest{}, "brad")
|
||||||
|
if result != "validation_error" || err == nil {
|
||||||
|
t.Fatalf("expected validation error, got result=%q err=%v", result, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("longhorn resolve error", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{
|
||||||
|
fakeKubeClient: &fakeKubeClient{},
|
||||||
|
resolveErr: errors.New("resolve backup pvc exploded"),
|
||||||
|
}},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
_, result, err := srv.executeBackup(context.Background(), api.BackupRequest{Namespace: "apps", PVC: "data"}, "brad")
|
||||||
|
if result != "validation_error" || err == nil || !strings.Contains(err.Error(), "resolve backup pvc exploded") {
|
||||||
|
t.Fatalf("expected resolve error, got result=%q err=%v", result, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("longhorn snapshot error", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{fakeKubeClient: &fakeKubeClient{}}},
|
||||||
|
&backupTestLonghornClient{
|
||||||
|
restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}},
|
||||||
|
createSnapErr: errors.New("create snapshot exploded"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
_, result, err := srv.executeBackup(context.Background(), api.BackupRequest{Namespace: "apps", PVC: "data"}, "brad")
|
||||||
|
if result != "backend_error" || err == nil || !strings.Contains(err.Error(), "create snapshot exploded") {
|
||||||
|
t.Fatalf("expected snapshot backend error, got result=%q err=%v", result, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("longhorn backup error", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{fakeKubeClient: &fakeKubeClient{}}},
|
||||||
|
&backupTestLonghornClient{
|
||||||
|
restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}},
|
||||||
|
snapshotErr: errors.New("snapshot backup exploded"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
_, result, err := srv.executeBackup(context.Background(), api.BackupRequest{Namespace: "apps", PVC: "data"}, "brad")
|
||||||
|
if result != "backend_error" || err == nil || !strings.Contains(err.Error(), "snapshot backup exploded") {
|
||||||
|
t.Fatalf("expected backup backend error, got result=%q err=%v", result, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("restic backend error", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "restic"},
|
||||||
|
&backupTestKubeClient{
|
||||||
|
restoreTestKubeClient: &restoreTestKubeClient{fakeKubeClient: &fakeKubeClient{}},
|
||||||
|
createBackupErr: errors.New("create backup job exploded"),
|
||||||
|
},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
_, result, err := srv.executeBackup(context.Background(), api.BackupRequest{Namespace: "apps", PVC: "data"}, "brad")
|
||||||
|
if result != "backend_error" || err == nil || !strings.Contains(err.Error(), "create backup job exploded") {
|
||||||
|
t.Fatalf("expected restic backend error, got result=%q err=%v", result, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unsupported driver", func(t *testing.T) {
|
||||||
|
srv := newBackupTestServer(
|
||||||
|
&config.Config{AuthRequired: false, BackupDriver: "mystery"},
|
||||||
|
&backupTestKubeClient{restoreTestKubeClient: &restoreTestKubeClient{fakeKubeClient: &fakeKubeClient{}}},
|
||||||
|
&backupTestLonghornClient{restoreTestLonghornClient: &restoreTestLonghornClient{fakeLonghornClient: &fakeLonghornClient{}}},
|
||||||
|
)
|
||||||
|
_, result, err := srv.executeBackup(context.Background(), api.BackupRequest{Namespace: "apps", PVC: "data"}, "brad")
|
||||||
|
if result != "unsupported_driver" || err == nil || !strings.Contains(err.Error(), "unsupported backup driver") {
|
||||||
|
t.Fatalf("expected unsupported driver error, got result=%q err=%v", result, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("status helpers", func(t *testing.T) {
|
||||||
|
testCases := map[string]int{
|
||||||
|
"validation_error": http.StatusBadRequest,
|
||||||
|
"unsupported_driver": http.StatusBadRequest,
|
||||||
|
"backend_error": http.StatusBadGateway,
|
||||||
|
"other": http.StatusInternalServerError,
|
||||||
|
}
|
||||||
|
for result, want := range testCases {
|
||||||
|
if got := backupStatusCode(result); got != want {
|
||||||
|
t.Fatalf("backupStatusCode(%q): expected %d, got %d", result, want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := namespaceResultStatus(true, 2, 2, 0); got != "dry_run" {
|
||||||
|
t.Fatalf("expected dry_run namespace result, got %q", got)
|
||||||
|
}
|
||||||
|
if got := namespaceResultStatus(false, 0, 0, 0); got != "empty" {
|
||||||
|
t.Fatalf("expected empty namespace result, got %q", got)
|
||||||
|
}
|
||||||
|
if got := namespaceResultStatus(false, 2, 2, 0); got != "success" {
|
||||||
|
t.Fatalf("expected success namespace result, got %q", got)
|
||||||
|
}
|
||||||
|
if got := namespaceResultStatus(false, 2, 0, 2); got != "failed" {
|
||||||
|
t.Fatalf("expected failed namespace result, got %q", got)
|
||||||
|
}
|
||||||
|
if got := namespaceResultStatus(false, 2, 1, 1); got != "partial" {
|
||||||
|
t.Fatalf("expected partial namespace result, got %q", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user