Files
go-sip/internal/agent/recovery_logging_test.go
T

134 lines
4.8 KiB
Go

package agent
import (
"bytes"
"context"
"errors"
"log/slog"
"os"
"path/filepath"
"strings"
"testing"
"time"
"git.ipao.vip/rogee/go-sip/internal/logging"
)
func captureRecoveryLogs(t *testing.T) *bytes.Buffer {
t.Helper()
prior := slog.Default()
out := new(bytes.Buffer)
logging.Configure("agent", false, out)
t.Cleanup(func() { slog.SetDefault(prior) })
return out
}
func TestRecoveryLogsEachOrphanCauseAndLeavesOriginalFiles(t *testing.T) {
out := captureRecoveryLogs(t)
root := privateRecoveryRoot(t)
recovery := RecordingRecovery{Root: root, writeState: func(string, any) error { return errors.New("metadata disk full") }}
entry := failedRecordingFixture()
if _, err := recovery.SaveFailure([]byte("RIFF private-audio"), entry, &UploadHTTPError{StatusCode: 503}); err == nil {
t.Fatal("fault hidden")
}
out.Reset()
recovery.writeState = nil
_, err := recovery.RecoverDue(context.Background(), nil, nil)
if !errors.Is(err, ErrRecordingRecoveryIncomplete) {
t.Fatal(err)
}
for _, part := range []string{"recovery_validate", "recovery_digest", "original audio has no call information"} {
if !strings.Contains(out.String(), part) {
t.Fatalf("missing %s: %s", part, out.String())
}
}
if strings.Contains(out.String(), "private-audio") {
t.Fatal("audio leaked")
}
if _, err := os.Stat(filepath.Join(root, entry.Bucket, filepath.FromSlash(entry.ObjectKey), "recording.wav")); err != nil {
t.Fatal(err)
}
}
func TestRecoveryLogsGrantCauseIdentityAndPersistedBackoff(t *testing.T) {
out := captureRecoveryLogs(t)
now := time.Date(2026, 10, 1, 10, 0, 0, 0, time.UTC)
recovery := RecordingRecovery{Root: privateRecoveryRoot(t), Now: func() time.Time { return now }}
entry, err := recovery.SaveFailure([]byte("RIFF private-audio"), failedRecordingFixture(), &UploadHTTPError{StatusCode: 503})
if err != nil {
t.Fatal(err)
}
now = now.Add(time.Minute)
out.Reset()
expected := errors.New("temporary Dispatcher outage")
_, err = recovery.RecoverDue(context.Background(), func(context.Context, RecordingRecoveryEntry) (RecoveryTarget, error) {
return RecoveryTarget{}, expected
}, func(context.Context, RecordingRecoveryEntry) error { return nil })
if !errors.Is(err, expected) {
t.Fatalf("original grant failure was lost: %v", err)
}
for _, part := range []string{entry.SourceEventID, entry.RecordingID, entry.UploadID, "request_upload_grant", "temporary Dispatcher outage", "next_attempt_at", `"attempts":2`} {
if !strings.Contains(out.String(), part) {
t.Fatalf("missing %s: %s", part, out.String())
}
}
pending, err := recovery.Load(entry.Bucket, entry.ObjectKey)
if err != nil || pending.Attempts != 2 || pending.State != "retry_pending" {
t.Fatal(pending, err)
}
out.Reset()
now = now.Add(time.Minute)
if _, err = recovery.RecoverDue(context.Background(), nil, nil); err != nil {
t.Fatal(err)
}
if !strings.Contains(out.String(), "retry_wait") || !strings.Contains(out.String(), entry.SourceEventID) {
t.Fatal(out.String())
}
}
func TestResultJournalLogsOriginalDecodeCause(t *testing.T) {
out := captureRecoveryLogs(t)
j := ResultJournal{Root: privateRecoveryRoot(t)}
entry := ResultEntry{SourceEventID: "corrupt-result-event", 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)
}
if err = os.WriteFile(path, []byte("{"), 0o600); err != nil {
t.Fatal(err)
}
err = j.Recover(context.Background(), func(context.Context, ResultEntry) error { return nil })
if err == nil || !strings.Contains(err.Error(), "unexpected end of JSON input") {
t.Fatalf("original decode reason was hidden: %v", err)
}
for _, part := range []string{"decode_original_result", "unexpected end of JSON input", "event_digest"} {
if !strings.Contains(out.String(), part) {
t.Fatalf("missing %s: %s", part, out.String())
}
}
if _, err = os.Stat(path); err != nil {
t.Fatal("corrupt original file must remain", err)
}
}
func TestResultJournalLogsIndividualCauseAndOriginalIdentity(t *testing.T) {
out := captureRecoveryLogs(t)
j := ResultJournal{Root: privateRecoveryRoot(t)}
entry := ResultEntry{SourceEventID: "result-log-event", DispatcherID: "11111111-1111-4111-8111-111111111111", TenantID: 1, Payload: []byte(`{"recording":{}}`)}
if err := j.SaveReady(entry); err != nil {
t.Fatal(err)
}
expected := errors.New("result persistence unavailable")
if err := j.Recover(context.Background(), func(context.Context, ResultEntry) error { return expected }); !errors.Is(err, expected) {
t.Fatal(err)
}
for _, part := range []string{"result-log-event", "report_original_result", "result persistence unavailable", "tenant_id"} {
if !strings.Contains(out.String(), part) {
t.Fatalf("missing %s: %s", part, out.String())
}
}
}