139 lines
4.5 KiB
Go
139 lines
4.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
|
|
)
|
|
|
|
func TestResultJournalResumesOnlyConfirmedResults(t *testing.T) {
|
|
root := t.TempDir()
|
|
if err := os.Chmod(root, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
j := ResultJournal{Root: root}
|
|
entry := ResultEntry{SourceEventID: "call-1", DispatcherID: "11111111-1111-4111-8111-111111111111", TenantID: 1, Payload: []byte(`{"event_id":"call-1","recording":{}}`)}
|
|
if err := j.SaveReady(entry); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
attempts := 0
|
|
restarted := ResultJournal{Root: root}
|
|
if err := restarted.Recover(context.Background(), func(_ context.Context, got ResultEntry) error {
|
|
attempts++
|
|
if got.SourceEventID != entry.SourceEventID || string(got.Payload) != string(entry.Payload) || got.Upload != nil {
|
|
t.Fatalf("changed original result: %+v", got)
|
|
}
|
|
if attempts == 1 {
|
|
return errors.New("Dispatcher response unknown")
|
|
}
|
|
return nil
|
|
}); err == nil {
|
|
t.Fatal("unconfirmed result must remain for a later retry")
|
|
}
|
|
if err := restarted.Recover(context.Background(), func(ctx context.Context, got ResultEntry) error {
|
|
attempts++
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if attempts != 2 {
|
|
t.Fatalf("expected one retry, got %d attempts", attempts)
|
|
}
|
|
if err := restarted.Recover(context.Background(), func(context.Context, ResultEntry) error { t.Fatal("confirmed result repeated"); return nil }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestResultJournalNeverReportsPreparedUpload(t *testing.T) {
|
|
root := t.TempDir()
|
|
if err := os.Chmod(root, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
j := ResultJournal{Root: root}
|
|
entry := ResultEntry{SourceEventID: "call-2", DispatcherID: "11111111-1111-4111-8111-111111111111", TenantID: 1, Payload: []byte(`{"event_id":"call-2","recording":{"status":"uploaded"}}`)}
|
|
if err := j.SavePrepared(entry); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := (&ResultJournal{Root: root}).Recover(context.Background(), func(context.Context, ResultEntry) error {
|
|
t.Fatal("a PUT with unknown outcome cannot generate a final result")
|
|
return nil
|
|
}); err == nil {
|
|
t.Fatal("prepared result must remain visibly blocked")
|
|
}
|
|
observation := &agentpb.UploadObservation{UploadId: "upload-2", RecordingId: "recording-2", PutStatusCode: 200, SizeBytes: 10, ChecksumSha256: "digest"}
|
|
if err := j.MarkUploaded("call-2", observation); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := j.DiscardPrepared("call-2"); err == nil {
|
|
t.Fatal("a ready result must not be discarded as an incomplete upload")
|
|
}
|
|
if err := (&ResultJournal{Root: root}).Recover(context.Background(), func(_ context.Context, got ResultEntry) error {
|
|
if got.Upload == nil || got.Upload.GetUploadId() != observation.GetUploadId() {
|
|
t.Fatal("uploaded proof missing after restart")
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.ReadDir(filepath.Join(root, ".results")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestResultJournalRejectsTrailingCorruptData(t *testing.T) {
|
|
root := t.TempDir()
|
|
if err := os.Chmod(root, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
j := ResultJournal{Root: root}
|
|
entry := ResultEntry{SourceEventID: "call-corrupt", DispatcherID: "11111111-1111-4111-8111-111111111111", TenantID: 1, Payload: []byte(`{"recording":{}}`)}
|
|
if err := j.SaveReady(entry); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path, err := j.path(entry.SourceEventID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0600)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := file.WriteString("\n{}"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := file.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := j.Recover(context.Background(), func(context.Context, ResultEntry) error {
|
|
t.Fatal("corrupt result was reported")
|
|
return nil
|
|
}); err == nil {
|
|
t.Fatal("corrupt result must remain visibly blocked")
|
|
}
|
|
}
|
|
|
|
func TestResultJournalDiscardPreparedAfterRecovery(t *testing.T) {
|
|
root := t.TempDir()
|
|
if err := os.Chmod(root, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
j := ResultJournal{Root: root}
|
|
entry := ResultEntry{SourceEventID: "call-3", DispatcherID: "11111111-1111-4111-8111-111111111111", TenantID: 1, Payload: []byte(`{"recording":{"status":"uploaded"}}`)}
|
|
if err := j.SavePrepared(entry); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := j.DiscardPrepared("call-3"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := j.DiscardPrepared("call-3"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if items, err := os.ReadDir(filepath.Join(root, ".results")); err != nil || len(items) != 0 {
|
|
t.Fatalf("confirmed recovered result still has a prepared journal: %v %v", items, err)
|
|
}
|
|
}
|