//go:build integration package process import ( "errors" "strings" "testing" "github.com/nats-io/nats.go" "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. The bucket exists (made by the admin) so the only reason Create // can fail is the missing grant, never "bucket not found"; and Conn's own ErrorHandler (conn.go // withRefusal/publishRefused) catches the denied publish at once, so this returns promptly rather than // after a timeout. func TestStateRefusesAnUngrantedBucketNamingIt(t *testing.T) { url, account := grantedSendProcess(t) _, adminJetStream := adminJS(t, url) if _, err := adminJetStream.CreateKeyValue(ctxFor(t), jetstream.KeyValueConfig{Bucket: "mesh-mail_unrelated"}); 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("unrelated") _, err = s.Create("key", map[string]any{"v": 1}) if err == nil { t.Fatal("writing to an ungranted bucket was not refused") } if !errors.Is(err, nats.ErrPermissionViolation) { t.Errorf("the refusal %v does not wrap nats.ErrPermissionViolation", err) } if !strings.Contains(err.Error(), "mesh-mail_unrelated") { t.Errorf("the refusal %q does not name the bucket mesh-mail_unrelated", err.Error()) } }