package stateobjects import ( "bytes" "strings" "testing" "time" ) // ADR 0666 §16: a reply or an event over the per-message limit carries the name of an object, and the receiver reads it // back whole; one that fits is sent as it is. func TestABodyOverTheMessageLimitIsSentAsAReference(t *testing.T) { m := newMemory() s := Sender{Objects: memObjects{m}, Store: "m_sent-objects"} fetch := ProcessFetch(memObjects{m}) for _, label := range []string{"reply.list", "event.done"} { body := []byte(`{"text":"` + strings.Repeat("y", 3<<20) + `"}`) sent, err := s.Send(label, body) if err != nil || len(sent) > MessageLimit { t.Fatalf("%s: sent %d bytes, %v", label, len(sent), err) } if _, isRef := ReadRef(sent); !isRef { t.Fatalf("%s: a 3 MiB body was sent whole", label) } got, err := Receive(sent, fetch) if err != nil || !bytes.Equal(got, body) { t.Fatalf("%s: received %d bytes, %v", label, len(got), err) } } small := []byte(`{"a":1}`) if sent, err := s.Send("x", small); err != nil || !bytes.Equal(sent, small) { t.Fatalf("a small body: %s, %v", sent, err) } if got, err := Receive(small, fetch); err != nil || !bytes.Equal(got, small) { t.Fatalf("receive a small body: %s, %v", got, err) } // an object that is not the one the reference names is refused sent, _ := s.Send("reply.again", bytes.Repeat([]byte("z"), 2<<20)) ref, _ := ReadRef(sent) m.objects[ref.Object] = []byte("tampered") if _, err := Receive(sent, fetch); err == nil { t.Fatal("a tampered object was accepted") } // a bundle's store takes at most what one runtime message carries bundle := Sender{Objects: BundleObjects{}, Store: "m_sent-objects"} if _, err := bundle.Send("reply.huge", make([]byte, BundleObjectMost+1)); err == nil { t.Fatal("a bundle sent a body its runtime cannot carry") } } // A bundle reads a sent object through the runtime's sent-get. func TestABundleReadsASentObjectThroughTheRuntime(t *testing.T) { m := newMemory() body := []byte(`{"text":"` + strings.Repeat("y", 3<<20) + `"}`) sent, err := Sender{Objects: memObjects{m}, Store: "m_sent-objects"}.Send("reply.list", body) if err != nil { t.Fatal(err) } out := runtimeOf(t, m, func() (any, error) { got, err := Receive(sent, BundleFetch) if err != nil || !bytes.Equal(got, body) { t.Errorf("received %d bytes, %v", len(got), err) } return "done", nil }) if !strings.Contains(out.(string), "done") { t.Fatalf("the bundle answered %v", out) } } // Design 47 G §410, §330: a foundation part's sender keeps the sequence of its sends in its old state section // `sent-log` and deletes the sent objects past 7 days at a later send, at most 20 a send. func TestAFoundationPartsSentStoreAgesFromItsSendLog(t *testing.T) { m := newMemory() clock := time.Date(2026, 11, 1, 0, 0, 0, 0, time.UTC) s := Sender{Objects: memObjects{m}, Store: "m_sent-objects", Log: m, Node: "n1", Now: func() time.Time { return clock }} var first string for i := 0; i < 25; i++ { sent, err := s.Send("reply."+string(rune('a'+i)), bytes.Repeat([]byte{byte('a' + i)}, 2<<20)) if err != nil { t.Fatal(err) } if ref, _ := ReadRef(sent); i == 0 { first = ref.Object } } if len(m.objects) != 25 { t.Fatalf("%d objects within 7 days, want 25", len(m.objects)) } clock = clock.Add(SentMaxAge + time.Hour) if _, err := s.Send("reply.late", bytes.Repeat([]byte("z"), 2<<20)); err != nil { t.Fatal(err) } if _, kept := m.objects[first]; kept || len(m.objects) != 6 { t.Fatalf("after a late send %d objects remain (the first kept: %v), want 6", len(m.objects), kept) } }