Files
orca/internal/transport/idempotency_test.go
T
Jon Chery df58bc25a3 docs(milestone): complete scheduling-streaming (v0.3)
---ci---
project: orca
phase: 3
milestone: v0.3
status: complete
requirements:
  covered: [REQ-022, REQ-030, REQ-032]
  partial: []
---/ci---

v0.3 milestone merged to main. Includes all v0.2 work (P08-P10) that
was previously on the milestone branch but not yet merged to main, plus
the v0.3 completion work (iter.Seq streaming + doctor network/db).

v0.2 phases included: P08 (mTLS), P09 (scheduling), P10 (security scan).
v0.3 phases: P0 (pre-execution), P1 (iter.Seq streaming), P2 (doctor),
P3 (final review+ship).

Total: 40 requirements, all complete. No new go.mod dependencies.
Full test suite passes under -race. gofmt + go vet clean.
2026-08-01 20:06:47 +00:00

134 lines
3.5 KiB
Go

package transport
import (
"context"
"errors"
"testing"
"time"
)
func TestIdempotencyStorePutGet(t *testing.T) {
s := NewIdempotencyStore()
if _, ok := s.Get("missing"); ok {
t.Fatal("expected missing key to return ok=false")
}
s.Put("k1", "job-1")
if jobID, ok := s.Get("k1"); !ok || jobID != "job-1" {
t.Errorf("Get(k1): got (%q, %v), want (job-1, true)", jobID, ok)
}
}
func TestIdempotencyStoreExpiry(t *testing.T) {
s := NewIdempotencyStore()
// Manually insert an expired entry.
s.entries["expired"] = dedupeEntry{
key: "expired",
jobID: "old-job",
expiresAt: time.Now().Add(-1 * time.Minute),
}
if _, ok := s.Get("expired"); ok {
t.Fatal("expected expired entry to return ok=false")
}
if _, exists := s.entries["expired"]; exists {
t.Error("expected expired entry to be removed by Get")
}
}
func TestIdempotencyStoreContext(t *testing.T) {
ctx := WithIdempotencyKey(context.Background(), "key-1")
if got := IdempotencyKeyFromContext(ctx); got != "key-1" {
t.Errorf("IdempotencyKeyFromContext: got %q, want key-1", got)
}
ctx2 := context.Background()
if got := IdempotencyKeyFromContext(ctx2); got != "" {
t.Errorf("IdempotencyKeyFromContext(empty): got %q, want \"\"", got)
}
}
func TestRetrySucceedsAfterTransient(t *testing.T) {
calls := 0
got, err := Do(context.Background(), DefaultRetryPolicy(),
func(_ context.Context, attempt int) (string, bool, error) {
calls++
if attempt < 3 {
return "", true, errors.New("connection refused: try again")
}
return "ok", true, nil
})
if err != nil {
t.Fatalf("Do: %v", err)
}
if got != "ok" {
t.Errorf("Do: got %q, want ok", got)
}
if calls != 3 {
t.Errorf("Do: got %d calls, want 3", calls)
}
}
func TestRetryNoKeyOnTransient(t *testing.T) {
// Without an idempotency key AND a non-idempotent verb, a
// transient error on the first attempt must NOT retry (REQ-037).
calls := 0
_, err := Do(context.Background(), DefaultRetryPolicy(),
func(_ context.Context, _ int) (string, bool, error) {
calls++
return "", false, errors.New("connection refused")
})
if err == nil {
t.Fatal("expected error, got nil")
}
if calls != 1 {
t.Errorf("expected 1 call (no retry without key), got %d", calls)
}
}
func TestRetryPermanentError(t *testing.T) {
calls := 0
_, err := Do(context.Background(), DefaultRetryPolicy(),
func(_ context.Context, _ int) (string, bool, error) {
calls++
return "", true, ErrPermanent
})
if !errors.Is(err, ErrPermanent) {
t.Errorf("expected ErrPermanent, got %v", err)
}
if calls != 1 {
t.Errorf("expected 1 call (permanent = no retry), got %d", calls)
}
}
func TestRetryContextCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel() // cancel immediately
calls := 0
_, err := Do(ctx, DefaultRetryPolicy(),
func(_ context.Context, _ int) (string, bool, error) {
calls++
return "", true, errors.New("EOF")
})
if !errors.Is(err, context.Canceled) {
t.Errorf("expected context.Canceled, got %v", err)
}
}
func TestIsTransient(t *testing.T) {
cases := []struct {
err error
want bool
}{
{nil, false},
{errors.New("connection refused"), true},
{errors.New("i/o timeout"), true},
{errors.New("EOF"), true},
{errors.New("no such host"), true},
{errors.New("connection reset by peer"), true},
{errors.New("invalid spec"), false},
}
for _, c := range cases {
if got := IsTransient(c.err); got != c.want {
t.Errorf("IsTransient(%v): got %v, want %v", c.err, got, c.want)
}
}
}