Files
go-sip/internal/agent/upload_failure_state_test.go
T

89 lines
3.3 KiB
Go

package agent
import (
"crypto/sha256"
"encoding/hex"
"errors"
"os"
"path/filepath"
"testing"
"time"
agentv1 "git.ipao.vip/rogee/go-sip/gen/agent/v1"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
)
func TestMockRecordingFailureFactSurvivesRestartWithoutAnotherPUT(t *testing.T) {
root := t.TempDir()
spool, err := NewSpool(root, nil)
if err != nil {
t.Fatal(err)
}
binding := &agentv1.ExecutionBinding{TenantId: "tenant-a", TenantKey: "tenant-a", ExecutionId: "execution-a"}
asset := &agentv1.AssetDescriptor{AssetId: "recording-a"}
requestID := uuid.NewString()
if err := spool.ClaimUpload(UploadAttempt{
RequestID: requestID, UploadID: "upload-a", Identity: "identity-a", State: "attempted",
Binding: binding, Asset: asset,
}); err != nil {
t.Fatal(err)
}
payload := []byte(`{"schema_version":"local-mock-recording-failure.v0.1","upload_id":"upload-a","recording_id":"recording-a","error_code":"upload_failed"}`)
sum := sha256.Sum256(payload)
fact := &agentv1.ExecutionFact{
FactId: uuid.NewString(), Binding: binding, Kind: agentv1.FactKind_FACT_KIND_RECORDING_PROGRESS,
PayloadJson: payload, ContentSha256: hex.EncodeToString(sum[:]), ObservedAtUnixMs: time.Now().UnixMilli(),
SourceBootId: uuid.NewString(), SourceSequence: 1,
}
if err := spool.RecordUploadFailureFact("upload-a", fact); err != nil {
t.Fatal(err)
}
restarted, err := NewSpool(root, nil)
if err != nil {
t.Fatal(err)
}
pending, err := restarted.PendingUploadFailures()
if err != nil || len(pending) != 1 || pending[0].FailureFact.GetFactId() != fact.FactId {
t.Fatalf("failure fact lost across restart: pending=%+v err=%v", pending, err)
}
if err := restarted.RecordUploadFailureFact("upload-a", fact); err != nil {
t.Fatalf("same fact must be idempotent: %v", err)
}
conflict := proto.Clone(fact).(*agentv1.ExecutionFact)
conflict.FactId = uuid.NewString()
if err := restarted.RecordUploadFailureFact("upload-a", conflict); err == nil {
t.Fatal("a second failure identity replaced the original")
}
if err := restarted.ReserveUploadRetry("upload-a", uuid.NewString()); err == nil {
t.Fatal("final failure still permitted an explicit new PUT")
}
if err := restarted.RecordUploadResult("upload-a", UploadResult{SizeBytes: 4, SHA256: "checksum", StatusCode: 200}); err == nil {
t.Fatal("failed upload was replaced by a success")
}
if err := restarted.CompleteUploadFailureReport("upload-a", fact.FactId); err != nil {
t.Fatal(err)
}
pending, err = restarted.PendingUploadFailures()
if err != nil || len(pending) != 0 {
t.Fatalf("acknowledged fact was redelivered: pending=%d err=%v", len(pending), err)
}
if err := restarted.CompleteUploadFailureReport("upload-a", fact.FactId); err != nil {
t.Fatalf("same acknowledgement must be idempotent: %v", err)
}
if err := restarted.CompleteUploadFailureReport("upload-a", uuid.NewString()); err == nil {
t.Fatal("unrelated acknowledgement closed failure fact")
}
persisted, err := restarted.LoadUploadAttempt("upload-a")
if err != nil || persisted.State != "attempted" || !persisted.FailureDelivered {
t.Fatalf("failed PUT attempt regressed: %+v err=%v", persisted, err)
}
data, err := os.ReadFile(filepath.Join(root, ".uploads", "upload-a", "state.json"))
if err != nil {
t.Fatal(err)
}
if len(data) == 0 || errors.Is(err, os.ErrNotExist) {
t.Fatal("failure fact was not durably journaled")
}
}