github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/pubsub/azuresb/azuresb_test.go (about)

     1  // Copyright 2018 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package azuresb
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"os"
    20  	"strings"
    21  	"sync/atomic"
    22  	"testing"
    23  
    24  	"gocloud.dev/internal/testing/setup"
    25  	"gocloud.dev/pubsub"
    26  	"gocloud.dev/pubsub/driver"
    27  	"gocloud.dev/pubsub/drivertest"
    28  
    29  	servicebus "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus"
    30  	"github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/admin"
    31  )
    32  
    33  var (
    34  	// See docs below on how to provision an Azure Service Bus Namespace and obtaining the connection string.
    35  	// https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-get-started-with-queues
    36  	connString = os.Getenv("SERVICEBUS_CONNECTION_STRING")
    37  )
    38  
    39  const (
    40  	nonexistentTopicName = "nonexistent-topic"
    41  
    42  	// Try to keep the entity name under Azure limits.
    43  	// https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-quotas
    44  	// says 50, but there appears to be some additional overhead. 40 works.
    45  	maxNameLen = 40
    46  )
    47  
    48  type harness struct {
    49  	adminClient *admin.Client
    50  	sbClient    *servicebus.Client
    51  	numTopics   uint32 // atomic
    52  	numSubs     uint32 // atomic
    53  	closer      func()
    54  	autodelete  bool
    55  	topics      map[driver.Topic]string
    56  }
    57  
    58  func newHarness(ctx context.Context, t *testing.T) (drivertest.Harness, error) {
    59  	if connString == "" {
    60  		return nil, fmt.Errorf("azuresb: test harness requires environment variable SERVICEBUS_CONNECTION_STRING to run")
    61  	}
    62  	adminClient, err := admin.NewClientFromConnectionString(connString, nil)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	sbClient, err := NewClientFromConnectionString(connString, nil)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	noop := func() {}
    71  	return &harness{
    72  		adminClient: adminClient,
    73  		sbClient:    sbClient,
    74  		closer:      noop,
    75  		topics:      map[driver.Topic]string{},
    76  	}, nil
    77  }
    78  
    79  func newHarnessUsingAutodelete(ctx context.Context, t *testing.T) (drivertest.Harness, error) {
    80  	h, err := newHarness(ctx, t)
    81  	if err == nil {
    82  		h.(*harness).autodelete = true
    83  	}
    84  	return h, err
    85  }
    86  
    87  func (h *harness) CreateTopic(ctx context.Context, testName string) (dt driver.Topic, cleanup func(), err error) {
    88  	topicName := sanitize(fmt.Sprintf("%s-top-%d", testName, atomic.AddUint32(&h.numTopics, 1)))
    89  	if err := createTopic(ctx, topicName, h.adminClient, nil); err != nil {
    90  		return nil, nil, err
    91  	}
    92  
    93  	sbSender, err := NewSender(h.sbClient, topicName, nil)
    94  	dt, err = openTopic(ctx, sbSender, nil)
    95  	if err != nil {
    96  		return nil, nil, err
    97  	}
    98  	h.topics[dt] = topicName
    99  	cleanup = func() {
   100  		sbSender.Close(ctx)
   101  		deleteTopic(ctx, topicName, h.adminClient)
   102  	}
   103  	return dt, cleanup, nil
   104  }
   105  
   106  func (h *harness) MakeNonexistentTopic(ctx context.Context) (driver.Topic, error) {
   107  	sbSender, err := NewSender(h.sbClient, nonexistentTopicName, nil)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	dt, err := openTopic(ctx, sbSender, nil)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	h.topics[dt] = nonexistentTopicName
   116  	return dt, nil
   117  }
   118  
   119  func (h *harness) CreateSubscription(ctx context.Context, dt driver.Topic, testName string) (ds driver.Subscription, cleanup func(), err error) {
   120  	subName := sanitize(fmt.Sprintf("%s-sub-%d", testName, atomic.AddUint32(&h.numSubs, 1)))
   121  	topicName := h.topics[dt]
   122  	err = createSubscription(ctx, topicName, subName, h.adminClient, nil)
   123  	if err != nil {
   124  		return nil, nil, err
   125  	}
   126  
   127  	var opts servicebus.ReceiverOptions
   128  	if h.autodelete {
   129  		opts.ReceiveMode = servicebus.ReceiveModeReceiveAndDelete
   130  	}
   131  	sbReceiver, err := NewReceiver(h.sbClient, topicName, subName, &opts)
   132  	if err != nil {
   133  		return nil, nil, err
   134  	}
   135  
   136  	sopts := SubscriptionOptions{}
   137  	if h.autodelete {
   138  		sopts.ReceiveAndDelete = true
   139  	}
   140  	ds, err = openSubscription(ctx, h.sbClient, sbReceiver, &sopts)
   141  	if err != nil {
   142  		return nil, nil, err
   143  	}
   144  
   145  	cleanup = func() {
   146  		sbReceiver.Close(ctx)
   147  		deleteSubscription(ctx, topicName, subName, h.adminClient)
   148  	}
   149  	return ds, cleanup, nil
   150  }
   151  
   152  func (h *harness) MakeNonexistentSubscription(ctx context.Context) (driver.Subscription, func(), error) {
   153  	const topicName = "topic-for-nonexistent-sub"
   154  	_, cleanup, err := h.CreateTopic(ctx, topicName)
   155  	if err != nil {
   156  		return nil, nil, err
   157  	}
   158  	sbReceiver, err := NewReceiver(h.sbClient, topicName, "nonexistent-subscription", nil)
   159  	if err != nil {
   160  		return nil, cleanup, err
   161  	}
   162  	sub, err := openSubscription(ctx, h.sbClient, sbReceiver, nil)
   163  	return sub, cleanup, err
   164  }
   165  
   166  func (h *harness) Close() {
   167  	h.closer()
   168  }
   169  
   170  func (h *harness) MaxBatchSizes() (int, int) { return sendBatcherOpts.MaxBatchSize, 0 }
   171  
   172  func (h *harness) SupportsMultipleSubscriptions() bool { return true }
   173  
   174  // Please run the TestConformance with an extended timeout since each test needs to perform CRUD for ServiceBus Topics and Subscriptions.
   175  // Example: C:\Go\bin\go.exe test -timeout 60s gocloud.dev/pubsub/azuresb -run ^TestConformance$
   176  func TestConformance(t *testing.T) {
   177  	if !*setup.Record {
   178  		t.Skip("replaying is not yet supported for Azure pubsub")
   179  	}
   180  	asTests := []drivertest.AsTest{sbAsTest{}}
   181  	drivertest.RunConformanceTests(t, newHarness, asTests)
   182  }
   183  
   184  func TestConformanceWithAutodelete(t *testing.T) {
   185  	if !*setup.Record {
   186  		t.Skip("replaying is not yet supported for Azure pubsub")
   187  	}
   188  	asTests := []drivertest.AsTest{sbAsTest{}}
   189  	drivertest.RunConformanceTests(t, newHarnessUsingAutodelete, asTests)
   190  }
   191  
   192  type sbAsTest struct{}
   193  
   194  func (sbAsTest) Name() string {
   195  	return "azure"
   196  }
   197  
   198  func (sbAsTest) TopicCheck(topic *pubsub.Topic) error {
   199  	var t2 servicebus.Sender
   200  	if topic.As(&t2) {
   201  		return fmt.Errorf("cast succeeded for %T, want failure", &t2)
   202  	}
   203  	var t3 *servicebus.Sender
   204  	if !topic.As(&t3) {
   205  		return fmt.Errorf("cast failed for %T", &t3)
   206  	}
   207  	return nil
   208  }
   209  
   210  func (sbAsTest) SubscriptionCheck(sub *pubsub.Subscription) error {
   211  	var s2 servicebus.Receiver
   212  	if sub.As(&s2) {
   213  		return fmt.Errorf("cast succeeded for %T, want failure", &s2)
   214  	}
   215  	var s3 *servicebus.Receiver
   216  	if !sub.As(&s3) {
   217  		return fmt.Errorf("cast failed for %T", &s3)
   218  	}
   219  	return nil
   220  }
   221  
   222  func (sbAsTest) TopicErrorCheck(t *pubsub.Topic, err error) error {
   223  	return nil
   224  }
   225  
   226  func (sbAsTest) SubscriptionErrorCheck(s *pubsub.Subscription, err error) error {
   227  	return nil
   228  }
   229  
   230  func (sbAsTest) MessageCheck(m *pubsub.Message) error {
   231  	var m2 servicebus.ReceivedMessage
   232  	if m.As(&m2) {
   233  		return fmt.Errorf("cast succeeded for %T, want failure", &m2)
   234  	}
   235  	var m3 *servicebus.ReceivedMessage
   236  	if !m.As(&m3) {
   237  		return fmt.Errorf("cast failed for %T", &m3)
   238  	}
   239  	return nil
   240  }
   241  
   242  func (sbAsTest) BeforeSend(as func(interface{}) bool) error {
   243  	var m *servicebus.Message
   244  	if !as(&m) {
   245  		return fmt.Errorf("cast failed for %T", &m)
   246  	}
   247  	return nil
   248  }
   249  
   250  func (sbAsTest) AfterSend(as func(interface{}) bool) error {
   251  	return nil
   252  }
   253  
   254  func sanitize(s string) string {
   255  	// First trim some not-so-useful strings that are part of all test names.
   256  	s = strings.Replace(s, "TestConformance/Test", "", 1)
   257  	s = strings.Replace(s, "TestConformanceWithAutodelete/Test", "", 1)
   258  	s = strings.Replace(s, "/", "_", -1)
   259  	if len(s) > maxNameLen {
   260  		// Drop prefix, not suffix, because suffix includes something to make
   261  		// entities unique within a test.
   262  		s = s[len(s)-maxNameLen:]
   263  	}
   264  	return s
   265  }
   266  
   267  // createTopic ensures the existence of a Service Bus Topic on a given Namespace.
   268  func createTopic(ctx context.Context, topicName string, adminClient *admin.Client, properties *admin.TopicProperties) error {
   269  	t, _ := adminClient.GetTopic(ctx, topicName, nil)
   270  	if t != nil {
   271  		_, _ = adminClient.DeleteTopic(ctx, topicName, nil)
   272  	}
   273  	opts := admin.CreateTopicOptions{
   274  		Properties: properties,
   275  	}
   276  	_, err := adminClient.CreateTopic(ctx, topicName, &opts)
   277  	return err
   278  }
   279  
   280  // deleteTopic removes a Service Bus Topic on a given Namespace.
   281  func deleteTopic(ctx context.Context, topicName string, adminClient *admin.Client) error {
   282  	t, _ := adminClient.GetTopic(ctx, topicName, nil)
   283  	if t != nil {
   284  		_, err := adminClient.DeleteTopic(ctx, topicName, nil)
   285  		return err
   286  	}
   287  	return nil
   288  }
   289  
   290  // createSubscription ensures the existence of a Service Bus Subscription on a given Namespace and Topic.
   291  func createSubscription(ctx context.Context, topicName string, subscriptionName string, adminClient *admin.Client, properties *admin.SubscriptionProperties) error {
   292  	s, _ := adminClient.GetSubscription(ctx, topicName, subscriptionName, nil)
   293  	if s != nil {
   294  		_, _ = adminClient.DeleteSubscription(ctx, topicName, subscriptionName, nil)
   295  	}
   296  	opts := admin.CreateSubscriptionOptions{
   297  		Properties: properties,
   298  	}
   299  	_, err := adminClient.CreateSubscription(ctx, topicName, subscriptionName, &opts)
   300  	return err
   301  }
   302  
   303  // deleteSubscription removes a Service Bus Subscription on a given Namespace and Topic.
   304  func deleteSubscription(ctx context.Context, topicName string, subscriptionName string, adminClient *admin.Client) error {
   305  	se, _ := adminClient.GetSubscription(ctx, topicName, subscriptionName, nil)
   306  	if se != nil {
   307  		_, err := adminClient.DeleteSubscription(ctx, topicName, subscriptionName, nil)
   308  		return err
   309  	}
   310  	return nil
   311  }
   312  
   313  func BenchmarkAzureServiceBusPubSub(b *testing.B) {
   314  	const (
   315  		benchmarkTopicName        = "benchmark-topic"
   316  		benchmarkSubscriptionName = "benchmark-subscription"
   317  	)
   318  	ctx := context.Background()
   319  
   320  	if connString == "" {
   321  		b.Fatal("azuresb: benchmark requires environment variable SERVICEBUS_CONNECTION_STRING to run")
   322  	}
   323  	adminClient, err := admin.NewClientFromConnectionString(connString, nil)
   324  	if err != nil {
   325  		b.Fatal(err)
   326  	}
   327  	sbClient, err := NewClientFromConnectionString(connString, nil)
   328  	if err != nil {
   329  		b.Fatal(err)
   330  	}
   331  
   332  	// Make topic.
   333  	if err := createTopic(ctx, benchmarkTopicName, adminClient, nil); err != nil {
   334  		b.Fatal(err)
   335  	}
   336  	defer deleteTopic(ctx, benchmarkTopicName, adminClient)
   337  
   338  	sbSender, err := NewSender(sbClient, benchmarkTopicName, nil)
   339  	if err != nil {
   340  		b.Fatal(err)
   341  	}
   342  	defer sbSender.Close(ctx)
   343  	topic, err := OpenTopic(ctx, sbSender, nil)
   344  	if err != nil {
   345  		b.Fatal(err)
   346  	}
   347  	defer topic.Shutdown(ctx)
   348  
   349  	// Make subscription.
   350  	if err := createSubscription(ctx, benchmarkTopicName, benchmarkSubscriptionName, adminClient, nil); err != nil {
   351  		b.Fatal(err)
   352  	}
   353  	sbReceiver, err := NewReceiver(sbClient, benchmarkTopicName, benchmarkSubscriptionName, nil)
   354  	if err != nil {
   355  		b.Fatal(err)
   356  	}
   357  	sub, err := OpenSubscription(ctx, sbClient, sbReceiver, nil)
   358  	if err != nil {
   359  		b.Fatal(err)
   360  	}
   361  	defer sub.Shutdown(ctx)
   362  
   363  	drivertest.RunBenchmarks(b, topic, sub)
   364  }
   365  
   366  func fakeConnectionStringInEnv() func() {
   367  	oldEnvVal := os.Getenv("SERVICEBUS_CONNECTION_STRING")
   368  	os.Setenv("SERVICEBUS_CONNECTION_STRING", "Endpoint=sb://foo.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=mykey")
   369  	return func() {
   370  		os.Setenv("SERVICEBUS_CONNECTION_STRING", oldEnvVal)
   371  	}
   372  }
   373  
   374  func TestOpenTopicFromURL(t *testing.T) {
   375  	cleanup := fakeConnectionStringInEnv()
   376  	defer cleanup()
   377  
   378  	tests := []struct {
   379  		URL     string
   380  		WantErr bool
   381  	}{
   382  		// OK.
   383  		{"azuresb://mytopic", false},
   384  		// Invalid parameter.
   385  		{"azuresb://mytopic?param=value", true},
   386  	}
   387  
   388  	ctx := context.Background()
   389  	for _, test := range tests {
   390  		topic, err := pubsub.OpenTopic(ctx, test.URL)
   391  		if (err != nil) != test.WantErr {
   392  			t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr)
   393  		}
   394  		if topic != nil {
   395  			topic.Shutdown(ctx)
   396  		}
   397  	}
   398  }
   399  
   400  func TestOpenSubscriptionFromURL(t *testing.T) {
   401  	cleanup := fakeConnectionStringInEnv()
   402  	defer cleanup()
   403  
   404  	tests := []struct {
   405  		URL     string
   406  		WantErr bool
   407  	}{
   408  		// OK.
   409  		{"azuresb://mytopic?subscription=mysub", false},
   410  		// Missing subscription.
   411  		{"azuresb://mytopic", true},
   412  		// Invalid parameter.
   413  		{"azuresb://mytopic?subscription=mysub&param=value", true},
   414  	}
   415  
   416  	ctx := context.Background()
   417  	for _, test := range tests {
   418  		sub, err := pubsub.OpenSubscription(ctx, test.URL)
   419  		if (err != nil) != test.WantErr {
   420  			t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr)
   421  		}
   422  		if sub != nil {
   423  			sub.Shutdown(ctx)
   424  		}
   425  	}
   426  }