89 lines
3.3 KiB
Go
89 lines
3.3 KiB
Go
package agent
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
|
|
"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 := &agentpb.ExecutionBinding{TenantId: "tenant-a", TenantKey: "tenant-a", ExecutionId: "execution-a"}
|
|
asset := &agentpb.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 := &agentpb.ExecutionFact{
|
|
FactId: uuid.NewString(), Binding: binding, Kind: agentpb.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).(*agentpb.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")
|
|
}
|
|
}
|