//go:build integration package process import ( "errors" "strings" "testing" "time" "github.com/nats-io/nats.go/jetstream" stdio "git.novox.be/novox/mesh-sdk/go" ) // 1188/AC1: given a process connection to a test bus granted the bucket mesh-mail_sends, when it creates a // key, reads it, and updates it with the revision it read, then each succeeds and returns the new // revision; an update with a stale revision fails as stdio.ErrChanged; a create of an existing key fails // as stdio.ErrChanged. func TestStateCreateGetUpdateOnAGrantedBucket(t *testing.T) { url, account := grantedSendProcess(t) _, adminJetStream := adminJS(t, url) if _, err := adminJetStream.CreateKeyValue(ctxFor(t), jetstream.KeyValueConfig{Bucket: "mesh-mail_sends"}); err != nil { t.Fatalf("the admin could not make the bucket: %v", err) } conn, err := Connect(account) if err != nil { t.Fatal(err) } defer conn.Close() s := conn.State("sends") rev, err := s.Create("key", map[string]any{"v": 1}) if err != nil { t.Fatalf("Create on a granted bucket: %v", err) } if rev == 0 { t.Fatal("Create returned no revision") } e, err := s.Get("key") if err != nil || e == nil { t.Fatalf("Get after Create: %+v, %v", e, err) } if e.Revision != rev || string(e.Value) != `{"v":1}` { t.Fatalf("Get read back %+v, want revision %d and {\"v\":1}", e, rev) } rev2, err := s.Update("key", map[string]any{"v": 2}, rev) if err != nil { t.Fatalf("Update at the revision read: %v", err) } if rev2 == rev { t.Fatal("Update did not return a new revision") } if _, err := s.Update("key", map[string]any{"v": 3}, rev); !errors.Is(err, stdio.ErrChanged) { t.Fatalf("Update at a stale revision: %v, want stdio.ErrChanged", err) } if _, err := s.Create("key", map[string]any{"v": 4}); !errors.Is(err, stdio.ErrChanged) { t.Fatalf("Create of an existing key: %v, want stdio.ErrChanged", err) } } // 1188/AC4: given an account not granted a bucket, when it writes there, then the bus's refusal comes back // as an error naming the bucket. A denied publish is refused only as an async protocol error with no // reply (never a reply naming the refusal), so StateTimeout is shortened for this test alone. func TestStateRefusesAnUngrantedBucketNamingIt(t *testing.T) { old := StateTimeout StateTimeout = 2 * time.Second t.Cleanup(func() { StateTimeout = old }) _, account := grantedSendProcess(t) conn, err := Connect(account) if err != nil { t.Fatal(err) } defer conn.Close() s := conn.State("unrelated") if _, err := s.Create("key", map[string]any{"v": 1}); err == nil { t.Fatal("writing to an ungranted bucket was not refused") } else if !strings.Contains(err.Error(), "mesh-mail_unrelated") { t.Errorf("the refusal %q does not name the bucket mesh-mail_unrelated", err.Error()) } }