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) } }