197 lines
8.8 KiB
Go
197 lines
8.8 KiB
Go
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func currentUploadedResultPayload(t *testing.T, binding CurrentRecordingGrant) []byte {
|
|
t.Helper()
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(currentResultPayload(t), &payload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
payload["outcome"] = "answered"
|
|
payload["reason_code"] = 200
|
|
payload["reason_message"] = "completed"
|
|
payload["recording"] = map[string]any{
|
|
"status": "uploaded", "bucket": binding.Bucket, "object_key": binding.ObjectKey,
|
|
"format": binding.Format, "channels": binding.Channels, "sample_rate_hz": binding.SampleRateHz,
|
|
"duration_ms": binding.DurationMS, "size_bytes": binding.SizeBytes,
|
|
"checksum_sha256": binding.ChecksumSHA256,
|
|
}
|
|
raw, err := json.Marshal(payload)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func currentUploadProof(binding CurrentRecordingGrant) CurrentUploadProof {
|
|
return CurrentUploadProof{UploadID: binding.UploadID, RecordingID: binding.RecordingID, StatusCode: 200, SizeBytes: binding.SizeBytes, SHA256: binding.ChecksumSHA256}
|
|
}
|
|
|
|
func TestCurrentUploadedResultRequiresOriginalGrantAndConfirmedPUT(t *testing.T) {
|
|
s := preparedCurrentCallStore(t)
|
|
cmd := currentResultCall(t, s, "uploaded-result-1", true)
|
|
binding := currentUploadBinding(cmd)
|
|
payload := currentUploadedResultPayload(t, binding)
|
|
proof := currentUploadProof(binding)
|
|
if _, _, err := s.RecordUploadedCallResult(cmd.DispatcherID, cmd.EventID, payload, proof); !errors.Is(err, ErrCurrentUploadUnverified) {
|
|
t.Fatalf("ungranted asset was reported as uploaded: %v", err)
|
|
}
|
|
if _, _, err := s.BindRecordingUpload(binding); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := s.RecordCallResult(cmd.DispatcherID, cmd.EventID, currentResultPayload(t)); !errors.Is(err, ErrCurrentUploadUnverified) {
|
|
t.Fatalf("failed OSS path bypassed itself with an empty recording: %v", err)
|
|
}
|
|
if _, _, err := s.RecordUploadedCallResult(cmd.DispatcherID, cmd.EventID, payload, CurrentUploadProof{}); !errors.Is(err, ErrCurrentUploadUnverified) {
|
|
t.Fatalf("missing PUT confirmation was accepted: %v", err)
|
|
}
|
|
original, created, err := s.RecordUploadedCallResult(cmd.DispatcherID, cmd.EventID, payload, proof)
|
|
if err != nil || !created || original.EventType != "call.execute.result" {
|
|
t.Fatalf("confirmed grant was not durably reported: created=%t event=%+v err=%v", created, original, err)
|
|
}
|
|
stored, err := s.LoadRecordingUpload(cmd.DispatcherID, cmd.EventID)
|
|
if err != nil || stored.ConfirmedAt == "" {
|
|
t.Fatalf("confirmed OSS fact was not committed with the outbox: stored=%+v err=%v", stored, err)
|
|
}
|
|
repeated, created, err := s.RecordUploadedCallResult(cmd.DispatcherID, cmd.EventID, payload, proof)
|
|
if err != nil || created || repeated.EventID != original.EventID {
|
|
t.Fatalf("same PUT fact created a second MQ result: created=%t err=%v", created, err)
|
|
}
|
|
if _, _, err := s.BindRecordingUpload(binding); !errors.Is(err, ErrCurrentUploadAlreadyConfirmed) {
|
|
t.Fatalf("confirmed upload received another grant: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCurrentUploadedResultRejectsChangedOSSMetadataOrProof(t *testing.T) {
|
|
s := preparedCurrentCallStore(t)
|
|
cmd := currentResultCall(t, s, "uploaded-changed-1", true)
|
|
binding := currentUploadBinding(cmd)
|
|
if _, _, err := s.BindRecordingUpload(binding); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
originalPayload := currentUploadedResultPayload(t, binding)
|
|
proof := currentUploadProof(binding)
|
|
for _, tc := range []struct {
|
|
field string
|
|
value any
|
|
}{
|
|
{"bucket", "other-bucket"}, {"object_key", "other/key.wav"}, {"checksum_sha256", strings.Repeat("b", 64)},
|
|
{"size_bytes", binding.SizeBytes + 1}, {"format", "mp3"}, {"channels", 2}, {"sample_rate_hz", 8000}, {"duration_ms", binding.DurationMS + 1},
|
|
} {
|
|
var result map[string]any
|
|
if err := json.Unmarshal(originalPayload, &result); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result["recording"].(map[string]any)[tc.field] = tc.value
|
|
altered, _ := json.Marshal(result)
|
|
if _, _, err := s.RecordUploadedCallResult(cmd.DispatcherID, cmd.EventID, altered, proof); err == nil {
|
|
t.Fatalf("changed %s was reported as the original upload", tc.field)
|
|
}
|
|
}
|
|
for _, change := range []struct {
|
|
name string
|
|
apply func(*CurrentUploadProof)
|
|
}{
|
|
{"status", func(p *CurrentUploadProof) { p.StatusCode = 503 }},
|
|
{"upload ID", func(p *CurrentUploadProof) { p.UploadID = "new-upload" }},
|
|
{"recording ID", func(p *CurrentUploadProof) { p.RecordingID = "new-recording" }},
|
|
{"checksum", func(p *CurrentUploadProof) { p.SHA256 = strings.Repeat("b", 64) }},
|
|
{"size", func(p *CurrentUploadProof) { p.SizeBytes++ }},
|
|
} {
|
|
altered := proof
|
|
change.apply(&altered)
|
|
if _, _, err := s.RecordUploadedCallResult(cmd.DispatcherID, cmd.EventID, originalPayload, altered); !errors.Is(err, ErrCurrentUploadUnverified) {
|
|
t.Fatalf("changed %s proof was accepted: %v", change.name, err)
|
|
}
|
|
}
|
|
if outbox, err := s.ListPendingOutbox(cmd.DispatcherID); err != nil || len(outbox) != 1 {
|
|
t.Fatalf("invalid upload escaped to SaaS: outbox=%+v err=%v", outbox, err)
|
|
}
|
|
}
|
|
|
|
func TestCurrentUploadedResultOutboxFailureDoesNotConfirmGrant(t *testing.T) {
|
|
s := preparedCurrentCallStore(t)
|
|
cmd := currentResultCall(t, s, "uploaded-outbox-fault-1", true)
|
|
binding := currentUploadBinding(cmd)
|
|
if _, _, err := s.BindRecordingUpload(binding); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.db.Exec(`CREATE TRIGGER fail_upload_final BEFORE INSERT ON dispatcher_outbox WHEN NEW.event_type='call.execute.result' BEGIN SELECT RAISE(ABORT,'injected outbox failure'); END`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
payload, proof := currentUploadedResultPayload(t, binding), currentUploadProof(binding)
|
|
if _, _, err := s.RecordUploadedCallResult(cmd.DispatcherID, cmd.EventID, payload, proof); err == nil {
|
|
t.Fatal("failed SQLite outbox was mistaken for delivered upload")
|
|
}
|
|
if stored, err := s.LoadRecordingUpload(cmd.DispatcherID, cmd.EventID); err != nil || stored.ConfirmedAt != "" {
|
|
t.Fatalf("partial confirmation escaped failed transaction: state=%+v err=%v", stored, err)
|
|
}
|
|
if _, err := s.db.Exec(`DROP TRIGGER fail_upload_final`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, created, err := s.RecordUploadedCallResult(cmd.DispatcherID, cmd.EventID, payload, proof); err != nil || !created {
|
|
t.Fatalf("same uploaded fact could not resume without another PUT: created=%t err=%v", created, err)
|
|
}
|
|
}
|
|
|
|
func TestCurrentUploadCannotBeginAfterEmptyFinalResult(t *testing.T) {
|
|
s := preparedCurrentCallStore(t)
|
|
cmd := currentResultCall(t, s, "empty-result-before-grant", true)
|
|
if _, _, err := s.RecordCallResult(cmd.DispatcherID, cmd.EventID, currentResultPayload(t)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := s.BindRecordingUpload(currentUploadBinding(cmd)); !errors.Is(err, ErrCurrentResultConflict) {
|
|
t.Fatalf("already final no-recording call gained a new upload target: %v", err)
|
|
}
|
|
if _, err := s.LoadRecordingUpload(cmd.DispatcherID, cmd.EventID); !errors.Is(err, ErrCurrentUploadNotFound) {
|
|
t.Fatalf("unexpected recording for finalized empty result: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCurrentUploadedResultRestartReusesGrantAndPendingOutbox(t *testing.T) {
|
|
s := preparedCurrentCallStore(t)
|
|
cmd := currentResultCall(t, s, "uploaded-restart-1", true)
|
|
binding := currentUploadBinding(cmd)
|
|
if _, _, err := s.BindRecordingUpload(binding); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
payload, proof := currentUploadedResultPayload(t, binding), currentUploadProof(binding)
|
|
original, created, err := s.RecordUploadedCallResult(cmd.DispatcherID, cmd.EventID, payload, proof)
|
|
if err != nil || !created {
|
|
t.Fatalf("persist original result: created=%t err=%v", created, err)
|
|
}
|
|
var seq int
|
|
var databaseName, databasePath string
|
|
if err := s.db.QueryRow(`PRAGMA database_list`).Scan(&seq, &databaseName, &databasePath); err != nil || databasePath == "" {
|
|
t.Fatalf("locate isolated database: %v", err)
|
|
}
|
|
if err := s.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
restarted, err := OpenCurrent(databasePath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer restarted.Close()
|
|
grant, err := restarted.LoadRecordingUpload(cmd.DispatcherID, cmd.EventID)
|
|
if err != nil || grant.ConfirmedAt == "" || grant.ObjectKey != binding.ObjectKey {
|
|
t.Fatalf("restarted D forgot uploaded fact or original OSS target: grant=%+v err=%v", grant, err)
|
|
}
|
|
pending, err := restarted.ListPendingOutbox(cmd.DispatcherID)
|
|
if err != nil || len(pending) != 2 || string(pending[1].Body) != string(original.Body) {
|
|
t.Fatalf("restarted D lost the sole undelivered MQ result: events=%+v err=%v", pending, err)
|
|
}
|
|
if repeated, created, err := restarted.RecordUploadedCallResult(cmd.DispatcherID, cmd.EventID, payload, proof); err != nil || created || repeated.EventID != original.EventID {
|
|
t.Fatalf("restarted D created a second result: created=%t err=%v", created, err)
|
|
}
|
|
if _, _, err := restarted.BindRecordingUpload(binding); !errors.Is(err, ErrCurrentUploadAlreadyConfirmed) {
|
|
t.Fatalf("restarted D issued another PUT grant: %v", err)
|
|
}
|
|
}
|