get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/test/services_test.go (about)

     1  // Copyright 2020 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package test
    15  
    16  import (
    17  	"bytes"
    18  	"fmt"
    19  	"strings"
    20  	"testing"
    21  	"time"
    22  
    23  	"get.pme.sh/pnats/server"
    24  	"github.com/nats-io/nats.go"
    25  )
    26  
    27  var basicMASetupContents = []byte(`
    28  	server_name: A
    29  	listen: 127.0.0.1:-1
    30  
    31  	accounts: {
    32  	    A: {
    33  	        users: [ {user: a, password: pwd} ]
    34  	        exports: [{service: "foo", response: stream}]
    35  	    },
    36  	    B: {
    37  	        users: [{user: b, password: pwd} ]
    38  		    imports: [{ service: { account: A, subject: "foo"}, to: "foo_request" }]
    39  	    }
    40  	}
    41  `)
    42  
    43  func TestServiceImportWithStreamed(t *testing.T) {
    44  	conf := createConfFile(t, basicMASetupContents)
    45  
    46  	srv, opts := RunServerWithConfig(conf)
    47  	defer srv.Shutdown()
    48  
    49  	// Limit max response maps here for the test.
    50  	accB, err := srv.LookupAccount("B")
    51  	if err != nil {
    52  		t.Fatalf("Error looking up account: %v", err)
    53  	}
    54  
    55  	// connect and offer a service
    56  	nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port))
    57  	if err != nil {
    58  		t.Fatalf("Error on connect: %v", err)
    59  	}
    60  	defer nc.Close()
    61  
    62  	nc.Subscribe("foo", func(msg *nats.Msg) {
    63  		if err := msg.Respond([]byte("world")); err != nil {
    64  			t.Fatalf("Error on respond: %v", err)
    65  		}
    66  	})
    67  	nc.Flush()
    68  
    69  	nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port))
    70  	if err != nil {
    71  		t.Fatalf("Error on connect: %v", err)
    72  	}
    73  	defer nc2.Close()
    74  
    75  	numRequests := 10
    76  	for i := 0; i < numRequests; i++ {
    77  		resp, err := nc2.Request("foo_request", []byte("hello"), 2*time.Second)
    78  		if err != nil {
    79  			t.Fatalf("Unexpected error: %v", err)
    80  		}
    81  		if resp == nil || strings.Compare("world", string(resp.Data)) != 0 {
    82  			t.Fatal("Did not receive the correct message")
    83  		}
    84  	}
    85  
    86  	// Since we are using a new client that multiplexes, until the client itself goes away
    87  	// we will have the full number of entries, even with the tighter ResponseEntriesPruneThreshold.
    88  	accA, err := srv.LookupAccount("A")
    89  	if err != nil {
    90  		t.Fatalf("Error looking up account: %v", err)
    91  	}
    92  
    93  	// These should always be the same now.
    94  	if nre := accB.NumPendingReverseResponses(); nre != numRequests {
    95  		t.Fatalf("Expected %d entries, got %d", numRequests, nre)
    96  	}
    97  	if nre := accA.NumPendingAllResponses(); nre != numRequests {
    98  		t.Fatalf("Expected %d entries, got %d", numRequests, nre)
    99  	}
   100  
   101  	// Now kill of the client that was doing the requests.
   102  	nc2.Close()
   103  
   104  	checkFor(t, time.Second, 10*time.Millisecond, func() error {
   105  		aNrssi := accA.NumPendingAllResponses()
   106  		bNre := accB.NumPendingReverseResponses()
   107  		if aNrssi != 0 || bNre != 0 {
   108  			return fmt.Errorf("Response imports and response entries should all be 0, got %d %d", aNrssi, bNre)
   109  		}
   110  		return nil
   111  	})
   112  
   113  	// Now let's test old style request and reply that uses a new inbox each time. This should work ok..
   114  	nc2, err = nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port), nats.UseOldRequestStyle())
   115  	if err != nil {
   116  		t.Fatalf("Error on connect: %v", err)
   117  	}
   118  	defer nc2.Close()
   119  
   120  	for i := 0; i < numRequests; i++ {
   121  		resp, err := nc2.Request("foo_request", []byte("hello"), 2*time.Second)
   122  		if err != nil {
   123  			t.Fatalf("Unexpected error: %v", err)
   124  		}
   125  		if resp == nil || strings.Compare("world", string(resp.Data)) != 0 {
   126  			t.Fatal("Did not receive the correct message")
   127  		}
   128  	}
   129  
   130  	checkFor(t, time.Second, 10*time.Millisecond, func() error {
   131  		aNrssi := accA.NumPendingAllResponses()
   132  		bNre := accB.NumPendingReverseResponses()
   133  		if aNrssi != 0 || bNre != 0 {
   134  			return fmt.Errorf("Response imports and response entries should all be 0, got %d %d", aNrssi, bNre)
   135  		}
   136  		return nil
   137  	})
   138  }
   139  
   140  func TestServiceImportWithStreamedResponseAndEOF(t *testing.T) {
   141  	conf := createConfFile(t, basicMASetupContents)
   142  
   143  	srv, opts := RunServerWithConfig(conf)
   144  	defer srv.Shutdown()
   145  
   146  	accA, err := srv.LookupAccount("A")
   147  	if err != nil {
   148  		t.Fatalf("Error looking up account: %v", err)
   149  	}
   150  	accB, err := srv.LookupAccount("B")
   151  	if err != nil {
   152  		t.Fatalf("Error looking up account: %v", err)
   153  	}
   154  
   155  	nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port))
   156  	if err != nil {
   157  		t.Fatalf("Error on connect: %v", err)
   158  	}
   159  	defer nc.Close()
   160  
   161  	// We will send four responses and then and nil message signaling EOF
   162  	nc.Subscribe("foo", func(msg *nats.Msg) {
   163  		// Streamed response.
   164  		msg.Respond([]byte("world-1"))
   165  		msg.Respond([]byte("world-2"))
   166  		msg.Respond([]byte("world-3"))
   167  		msg.Respond([]byte("world-4"))
   168  		msg.Respond(nil)
   169  	})
   170  	nc.Flush()
   171  
   172  	// Now setup requester.
   173  	nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port))
   174  	if err != nil {
   175  		t.Fatalf("Error on connect: %v", err)
   176  	}
   177  	defer nc2.Close()
   178  
   179  	numRequests := 10
   180  	expectedResponses := 5
   181  
   182  	for i := 0; i < numRequests; i++ {
   183  		// Create an inbox
   184  		reply := nats.NewInbox()
   185  		sub, _ := nc2.SubscribeSync(reply)
   186  		defer sub.Unsubscribe()
   187  
   188  		if err := nc2.PublishRequest("foo_request", reply, []byte("XOXO")); err != nil {
   189  			t.Fatalf("Error sending request: %v", err)
   190  		}
   191  
   192  		// Wait and make sure we get all the responses. Should be five.
   193  		checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
   194  			if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != expectedResponses {
   195  				return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, expectedResponses)
   196  			}
   197  			return nil
   198  		})
   199  	}
   200  
   201  	if nre := accA.NumPendingAllResponses(); nre != 0 {
   202  		t.Fatalf("Expected no entries, got %d", nre)
   203  	}
   204  	if nre := accB.NumPendingReverseResponses(); nre != 0 {
   205  		t.Fatalf("Expected no entries, got %d", nre)
   206  	}
   207  }
   208  
   209  func TestServiceExportsResponseFiltering(t *testing.T) {
   210  	conf := createConfFile(t, []byte(`
   211  		server_name: A
   212  		listen: 127.0.0.1:-1
   213  
   214  		accounts: {
   215  		    A: {
   216  		        users: [ {user: a, password: pwd} ]
   217  		        exports: [ {service: "foo"}, {service: "bar"} ]
   218  		    },
   219  		    B: {
   220  		        users: [{user: b, password: pwd} ]
   221  			    imports: [ {service: { account: A, subject: "foo"}}, {service: { account: A, subject: "bar"}, to: "baz"} ]
   222  		    }
   223  		}
   224  	`))
   225  
   226  	srv, opts := RunServerWithConfig(conf)
   227  	defer srv.Shutdown()
   228  
   229  	nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port))
   230  	if err != nil {
   231  		t.Fatalf("Error on connect: %v", err)
   232  	}
   233  	defer nc.Close()
   234  
   235  	// If we do not subscribe the system is now smart enough to not setup the response service imports.
   236  	nc.SubscribeSync("foo")
   237  	nc.SubscribeSync("bar")
   238  	nc.Flush()
   239  
   240  	nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port))
   241  	if err != nil {
   242  		t.Fatalf("Error on connect: %v", err)
   243  	}
   244  	defer nc2.Close()
   245  
   246  	// We don't expect responses, so just do publishes.
   247  	// 5 for foo
   248  	sendFoo := 5
   249  	for i := 0; i < sendFoo; i++ {
   250  		nc2.PublishRequest("foo", "reply", nil)
   251  	}
   252  	// 17 for bar
   253  	sendBar := 17
   254  	for i := 0; i < sendBar; i++ {
   255  		nc2.PublishRequest("baz", "reply", nil)
   256  	}
   257  	nc2.Flush()
   258  
   259  	accA, err := srv.LookupAccount("A")
   260  	if err != nil {
   261  		t.Fatalf("Error looking up account: %v", err)
   262  	}
   263  
   264  	sendTotal := sendFoo + sendBar
   265  	if nre := accA.NumPendingAllResponses(); nre != sendTotal {
   266  		t.Fatalf("Expected %d entries, got %d", sendTotal, nre)
   267  	}
   268  
   269  	if nre := accA.NumPendingResponses("foo"); nre != sendFoo {
   270  		t.Fatalf("Expected %d entries, got %d", sendFoo, nre)
   271  	}
   272  
   273  	if nre := accA.NumPendingResponses("bar"); nre != sendBar {
   274  		t.Fatalf("Expected %d entries, got %d", sendBar, nre)
   275  	}
   276  }
   277  
   278  func TestServiceExportsAutoDirectCleanup(t *testing.T) {
   279  	conf := createConfFile(t, []byte(`
   280  		listen: 127.0.0.1:-1
   281  		accounts: {
   282  		    A: {
   283  		        users: [ {user: a, password: pwd} ]
   284  		        exports: [ {service: "foo"} ]
   285  		    },
   286  		    B: {
   287  		        users: [{user: b, password: pwd} ]
   288  			    imports: [ {service: { account: A, subject: "foo"}} ]
   289  		    }
   290  		}
   291  	`))
   292  
   293  	srv, opts := RunServerWithConfig(conf)
   294  	defer srv.Shutdown()
   295  
   296  	acc, err := srv.LookupAccount("A")
   297  	if err != nil {
   298  		t.Fatalf("Error looking up account: %v", err)
   299  	}
   300  
   301  	// Potential resonder.
   302  	nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port))
   303  	if err != nil {
   304  		t.Fatalf("Error on connect: %v", err)
   305  	}
   306  	defer nc.Close()
   307  
   308  	// Requestor
   309  	nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port))
   310  	if err != nil {
   311  		t.Fatalf("Error on connect: %v", err)
   312  	}
   313  	defer nc2.Close()
   314  
   315  	expectNone := func() {
   316  		t.Helper()
   317  		if nre := acc.NumPendingAllResponses(); nre != 0 {
   318  			t.Fatalf("Expected no entries, got %d", nre)
   319  		}
   320  	}
   321  
   322  	toSend := 10
   323  
   324  	// With no responders we should never register service import responses etc.
   325  	for i := 0; i < toSend; i++ {
   326  		nc2.PublishRequest("foo", "reply", nil)
   327  	}
   328  	nc2.Flush()
   329  	expectNone()
   330  
   331  	// Now register a responder.
   332  	sub, _ := nc.Subscribe("foo", func(msg *nats.Msg) {
   333  		msg.Respond([]byte("world"))
   334  	})
   335  	nc.Flush()
   336  	defer sub.Unsubscribe()
   337  
   338  	// With no reply subject on a request we should never register service import responses etc.
   339  	for i := 0; i < toSend; i++ {
   340  		nc2.Publish("foo", nil)
   341  	}
   342  	nc2.Flush()
   343  	expectNone()
   344  
   345  	// Create an old request style client.
   346  	nc3, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port), nats.UseOldRequestStyle())
   347  	if err != nil {
   348  		t.Fatalf("Error on connect: %v", err)
   349  	}
   350  	defer nc3.Close()
   351  
   352  	// If the request loses interest before the response we should not queue up service import responses either.
   353  	// This only works for old style requests at the moment where we can detect interest going away.
   354  	delay := 25 * time.Millisecond
   355  	sub.Unsubscribe()
   356  	sub, _ = nc.Subscribe("foo", func(msg *nats.Msg) {
   357  		time.Sleep(delay)
   358  		msg.Respond([]byte("world"))
   359  	})
   360  	nc.Flush()
   361  	defer sub.Unsubscribe()
   362  
   363  	for i := 0; i < toSend; i++ {
   364  		nc3.Request("foo", nil, time.Millisecond)
   365  	}
   366  	nc3.Flush()
   367  	time.Sleep(time.Duration(toSend) * delay * 2)
   368  	expectNone()
   369  }
   370  
   371  // In some instances we do not have a forceful trigger that signals us to clean up.
   372  // Like a stream that does not send EOF or a responder who receives requests but does
   373  // not answer. For these we will have an expectation of a response threshold which
   374  // tells the system we should have seen a response by T, say 2 minutes, 30 seconds etc.
   375  func TestServiceExportsPruningCleanup(t *testing.T) {
   376  	conf := createConfFile(t, []byte(`
   377  		listen: 127.0.0.1:-1
   378  		accounts: {
   379  		    A: {
   380  		        users: [ {user: a, password: pwd} ]
   381  		        exports: [ {service: "foo", response: stream} ]
   382  		    },
   383  		    B: {
   384  		        users: [{user: b, password: pwd} ]
   385  			    imports: [ {service: { account: A, subject: "foo"}} ]
   386  		    }
   387  		}
   388  	`))
   389  
   390  	srv, opts := RunServerWithConfig(conf)
   391  	defer srv.Shutdown()
   392  
   393  	// Potential resonder.
   394  	nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port))
   395  	if err != nil {
   396  		t.Fatalf("Error on connect: %v", err)
   397  	}
   398  	defer nc.Close()
   399  
   400  	// We will subscribe but not answer.
   401  	sub, _ := nc.Subscribe("foo", func(msg *nats.Msg) {})
   402  	nc.Flush()
   403  	defer sub.Unsubscribe()
   404  
   405  	acc, err := srv.LookupAccount("A")
   406  	if err != nil {
   407  		t.Fatalf("Error looking up account: %v", err)
   408  	}
   409  
   410  	// Check on response thresholds.
   411  	rt, err := acc.ServiceExportResponseThreshold("foo")
   412  	if err != nil {
   413  		t.Fatalf("Error retrieving response threshold, %v", err)
   414  	}
   415  
   416  	if rt != server.DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD {
   417  		t.Fatalf("Expected the response threshold to be %v, got %v",
   418  			server.DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD, rt)
   419  	}
   420  	// now set it
   421  	newRt := 10 * time.Millisecond
   422  	if err := acc.SetServiceExportResponseThreshold("foo", newRt); err != nil {
   423  		t.Fatalf("Expected no error setting response threshold, got %v", err)
   424  	}
   425  
   426  	expectedPending := func(expected int) {
   427  		t.Helper()
   428  		// Caller is sleeping a bit before, but avoid flappers.
   429  		checkFor(t, time.Second, 15*time.Millisecond, func() error {
   430  			if nre := acc.NumPendingResponses("foo"); nre != expected {
   431  				return fmt.Errorf("Expected %d entries, got %d", expected, nre)
   432  			}
   433  			return nil
   434  		})
   435  	}
   436  
   437  	// Requestor
   438  	nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port))
   439  	if err != nil {
   440  		t.Fatalf("Error on connect: %v", err)
   441  	}
   442  	defer nc2.Close()
   443  
   444  	toSend := 10
   445  
   446  	// This should register and they will dangle. Make sure we clean them up.
   447  	for i := 0; i < toSend; i++ {
   448  		nc2.PublishRequest("foo", "reply", nil)
   449  	}
   450  	nc2.Flush()
   451  
   452  	expectedPending(10)
   453  	time.Sleep(4 * newRt)
   454  	expectedPending(0)
   455  
   456  	// Do it again.
   457  	for i := 0; i < toSend; i++ {
   458  		nc2.PublishRequest("foo", "reply", nil)
   459  	}
   460  	nc2.Flush()
   461  
   462  	expectedPending(10)
   463  	time.Sleep(4 * newRt)
   464  	expectedPending(0)
   465  }
   466  
   467  func TestServiceExportsResponseThreshold(t *testing.T) {
   468  	conf := createConfFile(t, []byte(`
   469  		listen: 127.0.0.1:-1
   470  		accounts: {
   471  		    A: {
   472  		        users: [ {user: a, password: pwd} ]
   473  		        exports: [ {service: "foo", response: stream, threshold: "1s"} ]
   474  		    },
   475  		}
   476  	`))
   477  
   478  	srv, opts := RunServerWithConfig(conf)
   479  	defer srv.Shutdown()
   480  
   481  	// Potential responder.
   482  	nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port))
   483  	if err != nil {
   484  		t.Fatalf("Error on connect: %v", err)
   485  	}
   486  	defer nc.Close()
   487  
   488  	// We will subscribe but not answer.
   489  	sub, _ := nc.Subscribe("foo", func(msg *nats.Msg) {})
   490  	nc.Flush()
   491  	defer sub.Unsubscribe()
   492  
   493  	acc, err := srv.LookupAccount("A")
   494  	if err != nil {
   495  		t.Fatalf("Error looking up account: %v", err)
   496  	}
   497  
   498  	// Check on response thresholds.
   499  	rt, err := acc.ServiceExportResponseThreshold("foo")
   500  	if err != nil {
   501  		t.Fatalf("Error retrieving response threshold, %v", err)
   502  	}
   503  	if rt != 1*time.Second {
   504  		t.Fatalf("Expected response threshold to be %v, got %v", 1*time.Second, rt)
   505  	}
   506  }
   507  
   508  func TestServiceExportsResponseThresholdChunked(t *testing.T) {
   509  	conf := createConfFile(t, []byte(`
   510  		listen: 127.0.0.1:-1
   511  		accounts: {
   512  		    A: {
   513  		        users: [ {user: a, password: pwd} ]
   514  		        exports: [ {service: "foo", response: chunked, threshold: "10ms"} ]
   515  		    },
   516  		    B: {
   517  		        users: [{user: b, password: pwd} ]
   518  			    imports: [ {service: { account: A, subject: "foo"}} ]
   519  		    }
   520  		}
   521  	`))
   522  
   523  	srv, opts := RunServerWithConfig(conf)
   524  	defer srv.Shutdown()
   525  
   526  	// Responder.
   527  	nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port))
   528  	if err != nil {
   529  		t.Fatalf("Error on connect: %v", err)
   530  	}
   531  	defer nc.Close()
   532  
   533  	numChunks := 10
   534  
   535  	// Respond with 5ms gaps for total response time for all chunks and EOF > 50ms.
   536  	nc.Subscribe("foo", func(msg *nats.Msg) {
   537  		// Streamed response.
   538  		for i := 1; i <= numChunks; i++ {
   539  			time.Sleep(5 * time.Millisecond)
   540  			msg.Respond([]byte(fmt.Sprintf("chunk-%d", i)))
   541  		}
   542  		msg.Respond(nil)
   543  	})
   544  	nc.Flush()
   545  
   546  	// Now setup requester.
   547  	nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port))
   548  	if err != nil {
   549  		t.Fatalf("Error on connect: %v", err)
   550  	}
   551  	defer nc2.Close()
   552  
   553  	// Create an inbox
   554  	reply := nats.NewInbox()
   555  	sub, _ := nc2.SubscribeSync(reply)
   556  	defer sub.Unsubscribe()
   557  
   558  	if err := nc2.PublishRequest("foo", reply, nil); err != nil {
   559  		t.Fatalf("Error sending request: %v", err)
   560  	}
   561  
   562  	checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
   563  		if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != numChunks+1 {
   564  			return fmt.Errorf("Did not receive correct number of chunks: %d vs %d", nmsgs, numChunks+1)
   565  		}
   566  		return nil
   567  	})
   568  }
   569  
   570  func TestServiceAllowResponsesPerms(t *testing.T) {
   571  	conf := createConfFile(t, []byte(`
   572  		listen: 127.0.0.1:-1
   573  		accounts: {
   574  		    A: {
   575  		        users: [ {user: a, password: pwd, permissions = {subscribe=foo, allow_responses=true}} ]
   576  		        exports: [ {service: "foo"} ]
   577  		    },
   578  		    B: {
   579  		        users: [{user: b, password: pwd} ]
   580  			    imports: [ {service: { account: A, subject: "foo"}} ]
   581  		    }
   582  		}
   583  	`))
   584  
   585  	srv, opts := RunServerWithConfig(conf)
   586  	defer srv.Shutdown()
   587  
   588  	// Responder.
   589  	nc, err := nats.Connect(fmt.Sprintf("nats://a:pwd@%s:%d", opts.Host, opts.Port))
   590  	if err != nil {
   591  		t.Fatalf("Error on connect: %v", err)
   592  	}
   593  	defer nc.Close()
   594  
   595  	reply := []byte("Hello")
   596  	// Respond with 5ms gaps for total response time for all chunks and EOF > 50ms.
   597  	nc.Subscribe("foo", func(msg *nats.Msg) {
   598  		msg.Respond(reply)
   599  	})
   600  	nc.Flush()
   601  
   602  	// Now setup requester.
   603  	nc2, err := nats.Connect(fmt.Sprintf("nats://b:pwd@%s:%d", opts.Host, opts.Port))
   604  	if err != nil {
   605  		t.Fatalf("Error on connect: %v", err)
   606  	}
   607  	defer nc2.Close()
   608  
   609  	resp, err := nc2.Request("foo", []byte("help"), time.Second)
   610  	if err != nil {
   611  		t.Fatalf("Error expecting response %v", err)
   612  	}
   613  	if !bytes.Equal(resp.Data, reply) {
   614  		t.Fatalf("Did not get correct response, %q vs %q", resp.Data, reply)
   615  	}
   616  }