package broker_test import ( "regexp" "testing" "github.com/novox/mesh-control/internal/broker" "github.com/novox/mesh-control/internal/link" ) // The names are written twice, so a test keeps them agreeing. // // `link` imports `broker`, so `broker` cannot import `link` — the queue and exchange names // therefore exist in both. A scoped account naming a queue nothing publishes to produces a // builder that takes no work and says nothing about why, which is the worst kind of silence. // // An external test package, because it may import both without either importing the other. func TestTheNamesTheBrokerScopesAreTheNamesTheLinkUses(t *testing.T) { for _, agreed := range []struct { what string scoped string actually string }{ {"the build queue", broker.BuildQueueName, link.BuildQueue}, {"the exchange", broker.ExchangeName, link.Exchange}, {"a node's queue", broker.QueueFor("somewhere"), link.QueueFor("somewhere")}, } { if agreed.scoped != agreed.actually { t.Errorf("%s: the broker scopes %q and the link uses %q", agreed.what, agreed.scoped, agreed.actually) } } } func TestABuilderMayWriteToAReplyQueueAndReadNoNodesDeclarations(t *testing.T) { // The scoping, checked as patterns rather than by connecting: what it may write must include // the queue an asker actually waits on, and what it may read must not include any node's. // // This exists because the first version scoped writes to `amq.gen-*` — the name one broker // happens to generate — and the builder built, could not answer, and the connection simply // closed saying only "not allowed to publish to exchange \'\'". Answering through the // exchange is what removed the need for any of that. write := regexp.MustCompile("^" + regexp.QuoteMeta(broker.ExchangeName) + "$") if !write.MatchString(broker.ExchangeName) { t.Error("a builder may not write to the exchange, so it can take work and never answer") } // And not the default exchange, where permission is per exchange rather than per queue — a // builder allowed to use it could publish into any node's queue. // // Confirmed against a real broker as well, and worth recording how that nearly went wrong: // an unconfirmed publish is asynchronous, so a refusal arrives as a channel close afterwards // and a naive check reports success. With publisher confirms the broker's refusal is // immediate. **A negative security assertion made against an asynchronous call is not an // assertion.** if write.MatchString("") { t.Error("a builder may publish to the default exchange, and so into any node's queue") } read := regexp.MustCompile("^" + regexp.QuoteMeta(broker.BuildQueueName) + "$") if !read.MatchString(broker.BuildQueueName) { t.Error("a builder may not read the build queue") } if read.MatchString(link.QueueFor("someone-else")) { // A build machine is not a node, and a node's queue carries its declarations. t.Error("a builder may read another machine's declarations") } }