test(soteria): cover B2 scan and remaining policy/k8s edges
This commit is contained in:
parent
5c9cca4420
commit
4a2febe148
@ -2,6 +2,7 @@ package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -12,7 +13,9 @@ import (
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
k8sfake "k8s.io/client-go/kubernetes/fake"
|
||||
k8stesting "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
func TestListBackupJobsAndListBackupJobsForPVCCoverFilteringAndSorting(t *testing.T) {
|
||||
@ -166,6 +169,48 @@ func TestResolvePVCMountedNodeIgnoresDeadPodsAndFindsMountedClaim(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadBackupJobLogCoversSuccessAndListFailures(t *testing.T) {
|
||||
client := &Client{Clientset: k8sfake.NewSimpleClientset(
|
||||
&corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "job-pod-old",
|
||||
Namespace: "apps",
|
||||
CreationTimestamp: metav1.NewTime(time.Now().UTC().Add(-2 * time.Hour)),
|
||||
Labels: map[string]string{"job-name": "backup-job"},
|
||||
},
|
||||
Status: corev1.PodStatus{StartTime: ptrTime(metav1.NewTime(time.Now().UTC().Add(-90 * time.Minute)))},
|
||||
},
|
||||
&corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "job-pod-new",
|
||||
Namespace: "apps",
|
||||
CreationTimestamp: metav1.NewTime(time.Now().UTC().Add(-1 * time.Hour)),
|
||||
Labels: map[string]string{"job-name": "backup-job"},
|
||||
},
|
||||
Status: corev1.PodStatus{StartTime: ptrTime(metav1.NewTime(time.Now().UTC().Add(-30 * time.Minute)))},
|
||||
},
|
||||
)}
|
||||
|
||||
logs, err := client.ReadBackupJobLog(context.Background(), "apps", "backup-job")
|
||||
if err != nil || logs != "fake logs" {
|
||||
t.Fatalf("expected fake pod logs response, got %q %v", logs, err)
|
||||
}
|
||||
|
||||
emptyClient := &Client{Clientset: k8sfake.NewSimpleClientset()}
|
||||
if _, err := emptyClient.ReadBackupJobLog(context.Background(), "apps", "backup-job"); err == nil || !strings.Contains(err.Error(), "no pod found") {
|
||||
t.Fatalf("expected missing pod error, got %v", err)
|
||||
}
|
||||
|
||||
clientset := k8sfake.NewSimpleClientset()
|
||||
clientset.PrependReactor("list", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, errors.New("list pods exploded")
|
||||
})
|
||||
listFailClient := &Client{Clientset: clientset}
|
||||
if _, err := listFailClient.ReadBackupJobLog(context.Background(), "apps", "backup-job"); err == nil || !strings.Contains(err.Error(), "list pods exploded") {
|
||||
t.Fatalf("expected wrapped pod list error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBackupJobCoversValidationDryRunAndLiveCreation(t *testing.T) {
|
||||
clientset := k8sfake.NewSimpleClientset(
|
||||
&corev1.Secret{
|
||||
@ -252,6 +297,73 @@ func TestCreateBackupJobCoversValidationDryRunAndLiveCreation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBackupJobCleansUpSecretOnJobCreateFailureAndSurfacesBindFailure(t *testing.T) {
|
||||
clientset := k8sfake.NewSimpleClientset(
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "restic-src", Namespace: "shared"},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"AWS_ACCESS_KEY_ID": []byte("abc"),
|
||||
"AWS_SECRET_ACCESS_KEY": []byte("def"),
|
||||
"RESTIC_PASSWORD": []byte("ghi"),
|
||||
},
|
||||
},
|
||||
)
|
||||
cfg := &config.Config{
|
||||
SecretNamespace: "shared",
|
||||
ResticSecretName: "restic-src",
|
||||
ResticRepository: "s3:https://repo/root",
|
||||
ResticImage: "restic/restic:latest",
|
||||
JobTTLSeconds: 3600,
|
||||
}
|
||||
|
||||
clientset.PrependReactor("create", "jobs", func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, errors.New("create job exploded")
|
||||
})
|
||||
client := &Client{Clientset: clientset}
|
||||
if _, secretName, err := client.CreateBackupJob(context.Background(), cfg, api.BackupRequest{
|
||||
Namespace: "apps",
|
||||
PVC: "data",
|
||||
}); err == nil || !strings.Contains(err.Error(), "create job exploded") {
|
||||
t.Fatalf("expected job create error, got secret=%q err=%v", secretName, err)
|
||||
}
|
||||
secrets, err := client.Clientset.CoreV1().Secrets("apps").List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("list secrets after failed backup create: %v", err)
|
||||
}
|
||||
if len(secrets.Items) != 0 {
|
||||
t.Fatalf("expected copied secret cleanup on create failure, got %#v", secrets.Items)
|
||||
}
|
||||
|
||||
bindFailClientset := k8sfake.NewSimpleClientset(
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "restic-src", Namespace: "shared"},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"AWS_ACCESS_KEY_ID": []byte("abc"),
|
||||
"AWS_SECRET_ACCESS_KEY": []byte("def"),
|
||||
"RESTIC_PASSWORD": []byte("ghi"),
|
||||
},
|
||||
},
|
||||
)
|
||||
bindFailClientset.PrependReactor("update", "secrets", func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||
update := action.(k8stesting.UpdateAction)
|
||||
secret := update.GetObject().(*corev1.Secret)
|
||||
if secret.Namespace == "apps" {
|
||||
return true, nil, errors.New("bind secret exploded")
|
||||
}
|
||||
return false, nil, nil
|
||||
})
|
||||
bindFailClient := &Client{Clientset: bindFailClientset}
|
||||
jobName, secretName, err := bindFailClient.CreateBackupJob(context.Background(), cfg, api.BackupRequest{
|
||||
Namespace: "apps",
|
||||
PVC: "data",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "bind secret exploded") || jobName == "" || secretName == "" {
|
||||
t.Fatalf("expected bind failure after backup job create, got job=%q secret=%q err=%v", jobName, secretName, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateRestoreJobCoversValidationDryRunAndLiveCreation(t *testing.T) {
|
||||
clientset := k8sfake.NewSimpleClientset(
|
||||
&corev1.Secret{
|
||||
@ -317,3 +429,67 @@ func TestCreateRestoreJobCoversValidationDryRunAndLiveCreation(t *testing.T) {
|
||||
t.Fatalf("expected restore job owner reference on copied secret, got %#v", secret.OwnerReferences)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateRestoreJobCleansUpSecretOnJobCreateFailureAndSurfacesBindFailure(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
SecretNamespace: "shared",
|
||||
ResticSecretName: "restic-src",
|
||||
ResticRepository: "s3:https://repo/root",
|
||||
ResticImage: "restic/restic:latest",
|
||||
JobTTLSeconds: 3600,
|
||||
}
|
||||
clientset := k8sfake.NewSimpleClientset(
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "restic-src", Namespace: "shared"},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"AWS_ACCESS_KEY_ID": []byte("abc"),
|
||||
"AWS_SECRET_ACCESS_KEY": []byte("def"),
|
||||
"RESTIC_PASSWORD": []byte("ghi"),
|
||||
},
|
||||
},
|
||||
)
|
||||
clientset.PrependReactor("create", "jobs", func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, errors.New("create restore job exploded")
|
||||
})
|
||||
client := &Client{Clientset: clientset}
|
||||
if _, _, err := client.CreateRestoreJob(context.Background(), cfg, api.RestoreTestRequest{Namespace: "apps"}); err == nil || !strings.Contains(err.Error(), "create restore job exploded") {
|
||||
t.Fatalf("expected restore job create error, got %v", err)
|
||||
}
|
||||
secrets, err := client.Clientset.CoreV1().Secrets("apps").List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("list secrets after failed restore create: %v", err)
|
||||
}
|
||||
if len(secrets.Items) != 0 {
|
||||
t.Fatalf("expected copied secret cleanup on restore create failure, got %#v", secrets.Items)
|
||||
}
|
||||
|
||||
bindFailClientset := k8sfake.NewSimpleClientset(
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "restic-src", Namespace: "shared"},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"AWS_ACCESS_KEY_ID": []byte("abc"),
|
||||
"AWS_SECRET_ACCESS_KEY": []byte("def"),
|
||||
"RESTIC_PASSWORD": []byte("ghi"),
|
||||
},
|
||||
},
|
||||
)
|
||||
bindFailClientset.PrependReactor("update", "secrets", func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||
update := action.(k8stesting.UpdateAction)
|
||||
secret := update.GetObject().(*corev1.Secret)
|
||||
if secret.Namespace == "apps" {
|
||||
return true, nil, errors.New("bind restore secret exploded")
|
||||
}
|
||||
return false, nil, nil
|
||||
})
|
||||
bindFailClient := &Client{Clientset: bindFailClientset}
|
||||
jobName, secretName, err := bindFailClient.CreateRestoreJob(context.Background(), cfg, api.RestoreTestRequest{Namespace: "apps"})
|
||||
if err == nil || !strings.Contains(err.Error(), "bind restore secret exploded") || jobName == "" || secretName == "" {
|
||||
t.Fatalf("expected restore bind failure after job create, got job=%q secret=%q err=%v", jobName, secretName, err)
|
||||
}
|
||||
}
|
||||
|
||||
func ptrTime(value metav1.Time) *metav1.Time {
|
||||
return &value
|
||||
}
|
||||
|
||||
144
internal/server/b2_scan_test.go
Normal file
144
internal/server/b2_scan_test.go
Normal file
@ -0,0 +1,144 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type fakeS3Object struct {
|
||||
key string
|
||||
size int64
|
||||
lastModified time.Time
|
||||
}
|
||||
|
||||
func newFakeS3Server(t *testing.T, buckets []string, objects map[string][]fakeS3Object, missing map[string]bool) *httptest.Server {
|
||||
t.Helper()
|
||||
|
||||
sort.Strings(buckets)
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/xml")
|
||||
switch {
|
||||
case r.URL.Path == "/" && r.URL.RawQuery == "":
|
||||
fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?><ListAllMyBucketsResult><Buckets>`)
|
||||
for _, bucket := range buckets {
|
||||
fmt.Fprintf(w, `<Bucket><Name>%s</Name><CreationDate>2026-04-20T00:00:00Z</CreationDate></Bucket>`, bucket)
|
||||
}
|
||||
fmt.Fprint(w, `</Buckets></ListAllMyBucketsResult>`)
|
||||
case strings.HasSuffix(r.URL.Path, "/") && r.URL.Query().Has("location"):
|
||||
fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?><LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">us-west-001</LocationConstraint>`)
|
||||
case strings.HasSuffix(r.URL.Path, "/") && r.URL.Query().Get("list-type") == "2":
|
||||
bucket := strings.Trim(strings.TrimSuffix(r.URL.Path, "/"), "/")
|
||||
if missing[bucket] {
|
||||
http.Error(w, "no such bucket", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, `<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Name>%s</Name><IsTruncated>false</IsTruncated>`, bucket)
|
||||
for _, object := range objects[bucket] {
|
||||
fmt.Fprintf(w, `<Contents><Key>%s</Key><LastModified>%s</LastModified><Size>%d</Size></Contents>`,
|
||||
object.key,
|
||||
object.lastModified.UTC().Format(time.RFC3339),
|
||||
object.size,
|
||||
)
|
||||
}
|
||||
fmt.Fprint(w, `</ListBucketResult>`)
|
||||
default:
|
||||
http.Error(w, "unexpected request", http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func TestScanB2UsageAutoDiscoversBucketsAndAggregatesObjects(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
server := newFakeS3Server(t,
|
||||
[]string{"zeta", "alpha"},
|
||||
map[string][]fakeS3Object{
|
||||
"alpha": {
|
||||
{key: "alpha-new", size: 12, lastModified: now.Add(-2 * time.Hour)},
|
||||
{key: "alpha-old", size: 30, lastModified: now.Add(-48 * time.Hour)},
|
||||
},
|
||||
"zeta": {
|
||||
{key: "zeta-new", size: 8, lastModified: now.Add(-1 * time.Hour)},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
defer server.Close()
|
||||
|
||||
result, err := scanB2Usage(context.Background(), b2Credentials{
|
||||
Endpoint: server.URL,
|
||||
Region: "us-west-001",
|
||||
AccessKeyID: "atlas-key",
|
||||
SecretAccessKey: "atlas-secret",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("scan b2 usage autodiscovery: %v", err)
|
||||
}
|
||||
|
||||
if !result.Enabled || !result.Available {
|
||||
t.Fatalf("expected available B2 usage result, got %#v", result)
|
||||
}
|
||||
if len(result.Buckets) != 2 || result.Buckets[0].Name != "alpha" || result.Buckets[1].Name != "zeta" {
|
||||
t.Fatalf("expected sorted bucket results, got %#v", result.Buckets)
|
||||
}
|
||||
if result.TotalObjects != 3 || result.TotalBytes != 50 {
|
||||
t.Fatalf("expected aggregated object totals, got %#v", result)
|
||||
}
|
||||
if result.RecentObjects24h != 2 || result.RecentBytes24h != 20 {
|
||||
t.Fatalf("expected recent object totals, got %#v", result)
|
||||
}
|
||||
if result.Buckets[0].RecentObjects24h != 1 || result.Buckets[0].RecentBytes24h != 12 || result.Buckets[0].LastModifiedAt == "" {
|
||||
t.Fatalf("expected alpha bucket recent stats, got %#v", result.Buckets[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanB2UsageConfiguredBucketsAndErrorBranches(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
server := newFakeS3Server(t,
|
||||
[]string{},
|
||||
map[string][]fakeS3Object{
|
||||
"alpha": {{key: "alpha", size: 1, lastModified: now.Add(-1 * time.Hour)}},
|
||||
"beta": {{key: "beta", size: 2, lastModified: now.Add(-2 * time.Hour)}},
|
||||
},
|
||||
map[string]bool{"missing": true},
|
||||
)
|
||||
defer server.Close()
|
||||
|
||||
result, err := scanB2Usage(context.Background(), b2Credentials{
|
||||
Endpoint: server.URL,
|
||||
Region: "us-west-001",
|
||||
AccessKeyID: "atlas-key",
|
||||
SecretAccessKey: "atlas-secret",
|
||||
}, []string{" beta ", "", "alpha"})
|
||||
if err != nil {
|
||||
t.Fatalf("scan b2 usage configured buckets: %v", err)
|
||||
}
|
||||
if len(result.Buckets) != 2 || result.Buckets[0].Name != "alpha" || result.Buckets[1].Name != "beta" {
|
||||
t.Fatalf("expected configured buckets to be trimmed and sorted, got %#v", result.Buckets)
|
||||
}
|
||||
|
||||
emptyServer := newFakeS3Server(t, nil, nil, nil)
|
||||
defer emptyServer.Close()
|
||||
if _, err := scanB2Usage(context.Background(), b2Credentials{
|
||||
Endpoint: emptyServer.URL,
|
||||
Region: "us-west-001",
|
||||
AccessKeyID: "atlas-key",
|
||||
SecretAccessKey: "atlas-secret",
|
||||
}, nil); err == nil || !strings.Contains(err.Error(), "no B2 buckets available for scan") {
|
||||
t.Fatalf("expected no-buckets error, got %v", err)
|
||||
}
|
||||
|
||||
if _, err := scanB2Usage(context.Background(), b2Credentials{
|
||||
Endpoint: server.URL,
|
||||
Region: "us-west-001",
|
||||
AccessKeyID: "atlas-key",
|
||||
SecretAccessKey: "atlas-secret",
|
||||
}, []string{"missing"}); err == nil || !strings.Contains(err.Error(), "scan B2 bucket missing") {
|
||||
t.Fatalf("expected bucket scan error, got %v", err)
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -183,3 +184,70 @@ func TestLoadPoliciesRejectsInvalidDocuments(t *testing.T) {
|
||||
t.Fatalf("expected invalid document error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPoliciesAppliesDefaultsAndSkipsInvalidEntries(t *testing.T) {
|
||||
srv := &Server{
|
||||
cfg: &config.Config{
|
||||
Namespace: "maintenance",
|
||||
PolicySecretName: "soteria-policies",
|
||||
},
|
||||
client: &fakeKubeClient{
|
||||
secretData: map[string][]byte{
|
||||
policySecretKey: []byte(`{
|
||||
"policies": [
|
||||
{"namespace":"apps","pvc":"data","interval_hours":0,"enabled":true},
|
||||
{"namespace":" ","pvc":"skip","interval_hours":4,"enabled":true},
|
||||
{"namespace":"ops","interval_hours":12,"enabled":false,"dedupe":false,"keep_last":3,"created_at":"2026-04-20T00:00:00Z","updated_at":"2026-04-20T01:00:00Z"}
|
||||
]
|
||||
}`),
|
||||
},
|
||||
},
|
||||
policies: map[string]api.BackupPolicy{},
|
||||
}
|
||||
|
||||
if err := srv.loadPolicies(context.Background()); err != nil {
|
||||
t.Fatalf("load valid policies: %v", err)
|
||||
}
|
||||
|
||||
if len(srv.policies) != 2 {
|
||||
t.Fatalf("expected two valid policies after filtering, got %#v", srv.policies)
|
||||
}
|
||||
|
||||
apps := srv.policies["apps__data"]
|
||||
if apps.IntervalHours != defaultPolicyHours || !apps.Dedupe || apps.KeepLast != 0 || !apps.Enabled {
|
||||
t.Fatalf("expected defaults for apps policy, got %#v", apps)
|
||||
}
|
||||
if apps.CreatedAt == "" || apps.UpdatedAt == "" {
|
||||
t.Fatalf("expected timestamps to default, got %#v", apps)
|
||||
}
|
||||
|
||||
ops := srv.policies["ops___all"]
|
||||
if ops.IntervalHours != 12 || ops.Dedupe || ops.KeepLast != 3 || ops.Enabled {
|
||||
t.Fatalf("expected explicit ops policy values to persist, got %#v", ops)
|
||||
}
|
||||
if ops.CreatedAt != "2026-04-20T00:00:00Z" || ops.UpdatedAt != "2026-04-20T01:00:00Z" {
|
||||
t.Fatalf("expected explicit timestamps to persist, got %#v", ops)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistPoliciesRejectsUnsupportedValues(t *testing.T) {
|
||||
srv := &Server{
|
||||
cfg: &config.Config{
|
||||
Namespace: "maintenance",
|
||||
PolicySecretName: "soteria-policies",
|
||||
},
|
||||
client: &policyTestKubeClient{fakeKubeClient: &fakeKubeClient{}},
|
||||
}
|
||||
|
||||
err := srv.persistPolicies(context.Background(), []api.BackupPolicy{
|
||||
{
|
||||
ID: "apps__data",
|
||||
Namespace: "apps",
|
||||
PVC: "data",
|
||||
IntervalHours: math.NaN(),
|
||||
},
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "encode policy document") {
|
||||
t.Fatalf("expected persist encode error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -437,13 +437,42 @@ func TestResolveB2CredentialsLoadsSecretValues(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveB2CredentialsRejectsMissingValues(t *testing.T) {
|
||||
srv := &Server{
|
||||
cfg: &config.Config{},
|
||||
client: &fakeKubeClient{},
|
||||
testCases := []struct {
|
||||
name string
|
||||
cfg config.Config
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "missing endpoint",
|
||||
cfg: config.Config{},
|
||||
want: "B2 endpoint is not configured",
|
||||
},
|
||||
{
|
||||
name: "missing access key",
|
||||
cfg: config.Config{
|
||||
B2Endpoint: "https://s3.us-west-000.backblazeb2.com",
|
||||
B2SecretAccessKey: "def",
|
||||
},
|
||||
want: "B2 access key ID is not configured",
|
||||
},
|
||||
{
|
||||
name: "missing secret key",
|
||||
cfg: config.Config{
|
||||
B2Endpoint: "https://s3.us-west-000.backblazeb2.com",
|
||||
B2AccessKeyID: "abc",
|
||||
},
|
||||
want: "B2 secret access key is not configured",
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := srv.resolveB2Credentials(context.Background()); err == nil || !strings.Contains(err.Error(), "B2 endpoint is not configured") {
|
||||
t.Fatalf("expected missing endpoint error, got %v", err)
|
||||
for _, tc := range testCases {
|
||||
srv := &Server{
|
||||
cfg: &tc.cfg,
|
||||
client: &fakeKubeClient{},
|
||||
}
|
||||
if _, err := srv.resolveB2Credentials(context.Background()); err == nil || !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("%s: expected error containing %q, got %v", tc.name, tc.want, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user