feat: persist resumable client script uploads

This commit is contained in:
2026-09-06 06:39:12 +00:00
parent 6539b9e965
commit 62c0052979
5 changed files with 447 additions and 2 deletions
+110
View File
@@ -0,0 +1,110 @@
package spool
import (
"context"
"crypto/sha256"
"errors"
"path/filepath"
"testing"
"time"
)
func TestScriptUploadDurableExactReplayAndCommit_HP_SCRIPT_01(t *testing.T) {
t.Parallel()
ctx := context.Background()
directory := filepath.Join(t.TempDir(), "spool")
store := openTestStore(t, ctx, directory, DefaultTombstoneLimit)
issue := testUUID(t, "019c46f1-1d02-7000-8000-000000000031")
if _, err := store.AcceptCommand(ctx, testCommand(issue, []byte("script command")), time.Now().UTC()); err != nil {
t.Fatal(err)
}
raw := []byte("first chunk; second chunk")
descriptor := ScriptDescriptor{SizeBytes: uint64(len(raw)), SHA256: sha256.Sum256(raw)}
status, err := store.BeginScript(ctx, issue, descriptor)
if err != nil || status.ReceivedBytes != 0 || status.Committed {
t.Fatalf("BeginScript = %#v, %v", status, err)
}
duplicate, err := store.BeginScript(ctx, issue, descriptor)
if err != nil || !duplicate.Duplicate {
t.Fatalf("matching BeginScript replay = %#v, %v", duplicate, err)
}
first := raw[:12]
status, err = store.AppendScriptChunk(ctx, issue, 0, first, sha256.Sum256(first))
if err != nil || status.ReceivedBytes != uint64(len(first)) {
t.Fatalf("first ScriptChunk = %#v, %v", status, err)
}
duplicate, err = store.AppendScriptChunk(ctx, issue, 0, first, sha256.Sum256(first))
if err != nil || !duplicate.Duplicate || duplicate.ReceivedBytes != uint64(len(first)) {
t.Fatalf("matching ScriptChunk replay = %#v, %v", duplicate, err)
}
if _, err := store.AppendScriptChunk(ctx, issue, uint64(len(first)+1), raw[len(first):], sha256.Sum256(raw[len(first):])); !errors.Is(err, ErrScriptHole) {
t.Fatalf("script hole error = %v, want ErrScriptHole", err)
}
if _, err := store.AppendScriptChunk(ctx, issue, 0, []byte("different one"), sha256.Sum256([]byte("different one"))); !errors.Is(err, ErrScriptOverlap) {
t.Fatalf("changed overlap error = %v, want ErrScriptOverlap", err)
}
if _, err := store.CommitScript(ctx, issue, descriptor); !errors.Is(err, ErrScriptBounds) {
t.Fatalf("early commit error = %v, want ErrScriptBounds", err)
}
if err := store.Close(); err != nil {
t.Fatal(err)
}
store = openTestStore(t, ctx, directory, DefaultTombstoneLimit)
rest := raw[len(first):]
status, err = store.AppendScriptChunk(ctx, issue, uint64(len(first)), rest, sha256.Sum256(rest))
if err != nil || status.ReceivedBytes != uint64(len(raw)) {
t.Fatalf("resumed ScriptChunk = %#v, %v", status, err)
}
status, err = store.CommitScript(ctx, issue, descriptor)
if err != nil || !status.Committed {
t.Fatalf("ScriptCommit = %#v, %v", status, err)
}
duplicate, err = store.CommitScript(ctx, issue, descriptor)
if err != nil || !duplicate.Duplicate || !duplicate.Committed {
t.Fatalf("ScriptCommit replay = %#v, %v", duplicate, err)
}
if _, err := store.AppendScriptChunk(ctx, issue, uint64(len(raw)), []byte("!"), sha256.Sum256([]byte("!"))); !errors.Is(err, ErrScriptTerminal) {
t.Fatalf("post-commit chunk error = %v, want ErrScriptTerminal", err)
}
var compression uint32
var stored []byte
if err := store.db.QueryRowContext(ctx, `SELECT compression, stored_data FROM scripts WHERE issue_uuid = ?`, issue[:]).Scan(&compression, &stored); err != nil {
t.Fatal(err)
}
if compression != 2 || string(stored) == string(raw) {
t.Fatalf("script was not stored as zstd data: compression=%d stored=%q", compression, stored)
}
}
func TestScriptUploadRejectsDescriptorDigestAndBounds_BH_SCRIPT_01(t *testing.T) {
t.Parallel()
ctx := context.Background()
store := openTestStore(t, ctx, filepath.Join(t.TempDir(), "spool"), DefaultTombstoneLimit)
issue := testUUID(t, "019c46f1-1d02-7000-8000-000000000032")
if _, err := store.AcceptCommand(ctx, testCommand(issue, []byte("script command")), time.Now().UTC()); err != nil {
t.Fatal(err)
}
raw := []byte("abc")
descriptor := ScriptDescriptor{SizeBytes: uint64(len(raw)), SHA256: sha256.Sum256(raw)}
if _, err := store.BeginScript(ctx, issue, descriptor); err != nil {
t.Fatal(err)
}
wrong := sha256.Sum256([]byte("wrong"))
if _, err := store.AppendScriptChunk(ctx, issue, 0, raw, wrong); !errors.Is(err, ErrScriptDigest) {
t.Fatalf("chunk digest error = %v, want ErrScriptDigest", err)
}
if _, err := store.AppendScriptChunk(ctx, issue, 0, []byte("abcd"), sha256.Sum256([]byte("abcd"))); !errors.Is(err, ErrScriptBounds) {
t.Fatalf("out-of-bounds chunk error = %v, want ErrScriptBounds", err)
}
conflicting := descriptor
conflicting.SHA256 = wrong
if _, err := store.BeginScript(ctx, issue, conflicting); !errors.Is(err, ErrScriptConflict) {
t.Fatalf("descriptor conflict error = %v, want ErrScriptConflict", err)
}
if err := store.MarkTerminal(ctx, issue, 5); err != nil {
t.Fatal(err)
}
if _, err := store.AppendScriptChunk(ctx, issue, 0, raw, sha256.Sum256(raw)); !errors.Is(err, ErrScriptTerminal) {
t.Fatalf("terminal command chunk error = %v, want ErrScriptTerminal", err)
}
}