Scert/backend/internal/deployment/recovery_inspection_cancel_test.go

91 lines
3.0 KiB
Go

package deployment
import (
"context"
"errors"
"reflect"
"strings"
"testing"
"time"
)
type inspectionCancelClient struct {
*inspectionClient
cancel context.CancelFunc
stage string
}
func (c *inspectionCancelClient) Exists(ctx context.Context, path string) (bool, error) {
if c.stage == "exists" || (c.stage == "lock-exists" && strings.HasSuffix(path, ".scert-lock")) {
c.cancel()
return false, errors.New("sensitive-read-error")
}
return c.memoryClient.Exists(ctx, path)
}
func (c *inspectionCancelClient) FileDigest(ctx context.Context, path string) (string, error) {
if c.stage == "digest" {
c.cancel()
return "", errors.New("sensitive-read-error")
}
return c.memoryClient.FileDigest(ctx, path)
}
func (c *inspectionCancelClient) ReadLockOwner(ctx context.Context, path string) (string, error) {
if c.stage == "owner" {
c.cancel()
return "", errors.New("sensitive-read-error")
}
return c.memoryClient.ReadLockOwner(ctx, path)
}
func TestRecoveryInspectionCancellationBoundaries(t *testing.T) {
for _, stage := range []string{"resolve", "exists", "digest", "lock-exists", "owner"} {
t.Run(stage, func(t *testing.T) {
server, memory, record := recoveryPreflightFixture()
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
client := &inspectionCancelClient{inspectionClient: &inspectionClient{memoryClient: memory}, cancel: cancel, stage: stage}
journal := &memoryTransactionJournal{pending: []TransactionRecord{record}}
factory := &inspectionFactory{client: client}
service := NewServiceWithJournal(factory, journal)
result, err := service.InspectPending(ctx, record.ID, func(ctx context.Context, id string) (Server, error) {
deadline, ok := ctx.Deadline()
if !ok || time.Until(deadline) > 30*time.Second {
t.Fatal("inspection lacks bounded deadline")
}
if stage == "resolve" {
cancel()
return Server{}, errors.New("sensitive-resolve-error")
}
return server, nil
})
if !errors.Is(err, context.Canceled) || !reflect.DeepEqual(result, RecoveryInspection{}) {
t.Fatalf("cancellation lost: %+v %v", result, err)
}
if client.closed != factory.calls || client.mutations != 0 || len(journal.updated) != 0 || len(journal.finished) != 0 {
t.Fatal("cancellation changed state or leaked client")
}
})
}
}
func TestRecoveryInspectionPreservesEarlierDeadline(t *testing.T) {
server, memory, record := recoveryPreflightFixture()
ctx, cancel := context.WithTimeout(t.Context(), time.Second)
defer cancel()
deadline, _ := ctx.Deadline()
client := &inspectionClient{memoryClient: memory}
service := NewServiceWithJournal(fixedClientFactory{client}, &memoryTransactionJournal{pending: []TransactionRecord{record}})
_, err := service.InspectPending(ctx, record.ID, func(ctx context.Context, id string) (Server, error) {
actual, ok := ctx.Deadline()
if !ok || !actual.Equal(deadline) {
t.Fatal("caller deadline extended")
}
return server, nil
})
if err != nil {
t.Fatal(err)
}
}