@@ -72,7 +72,7 @@ func (suite *tsoAPITestSuite) SetupTest() {
7272 pdLeaderServer := suite .pdCluster .GetServer (leaderName )
7373 re .NoError (pdLeaderServer .BootstrapCluster ())
7474 suite .backendEndpoints = pdLeaderServer .GetAddr ()
75- suite .tsoCluster , err = tests .NewTestTSOCluster (suite .ctx , 1 , suite .backendEndpoints )
75+ suite .tsoCluster , err = tests .NewTestTSOCluster (suite .ctx , 2 , suite .backendEndpoints )
7676 re .NoError (err )
7777}
7878
@@ -297,3 +297,62 @@ func (suite *tsoAPITestSuite) TestHealth() {
297297 defer resp .Body .Close ()
298298 re .Equal (http .StatusOK , resp .StatusCode )
299299}
300+
301+ // TestForwardingBehavior specifically tests the API forwarding logic.
302+ func (suite * tsoAPITestSuite ) TestForwardingBehavior () {
303+ re := suite .Require ()
304+
305+ primary := suite .tsoCluster .WaitForDefaultPrimaryServing (re )
306+ re .NotNil (primary )
307+ var follower * tso.Server
308+ for _ , srv := range suite .tsoCluster .GetServers () {
309+ if srv .Name () != primary .Name () {
310+ follower = srv
311+ break
312+ }
313+ }
314+ re .NotNil (follower )
315+ re .True (primary .IsServing ())
316+ re .False (follower .IsServing ())
317+ re .NotEqual (follower .GetConfig ().GetListenAddr (), primary .GetConfig ().GetListenAddr ())
318+
319+ followerAddr := follower .GetAddr ()
320+ followerURL := func (path string ) string {
321+ return fmt .Sprintf ("%s%s%s" , followerAddr , apis .APIPathPrefix , path )
322+ }
323+
324+ // Test: PUT /admin/log should be handled by the follower locally.
325+ logURL := followerURL ("/admin/log" )
326+ level := "debug"
327+ logPayload , err := json .Marshal (level )
328+ re .NoError (err )
329+ req , _ := http .NewRequest (http .MethodPut , logURL , bytes .NewBuffer (logPayload ))
330+ req .Header .Set ("Content-Type" , "application/json" )
331+ resp , err := tests .TestDialClient .Do (req )
332+ re .NoError (err )
333+ defer resp .Body .Close ()
334+ re .Equal (http .StatusOK , resp .StatusCode )
335+
336+ // Test: GET /config should be handled by the follower locally.
337+ configURL := followerURL ("/config" )
338+ var followerCfg tso.Config
339+ err = testutil .ReadGetJSON (re , tests .TestDialClient , configURL , & followerCfg )
340+ re .NoError (err )
341+ re .Equal (follower .GetConfig ().GetListenAddr (), followerCfg .GetListenAddr ())
342+ re .NotEqual (primary .GetConfig ().GetListenAddr (), followerCfg .GetListenAddr ())
343+ re .Equal (level , followerCfg .Log .Level )
344+
345+ // Test: GET /keyspace-groups/members should be handled by the follower locally.
346+ membersURL := followerURL ("/keyspace-groups/members" )
347+ var kgms map [uint32 ]* apis.KeyspaceGroupMember
348+ err = testutil .ReadGetJSON (re , tests .TestDialClient , membersURL , & kgms )
349+ re .NoError (err )
350+ re .Len (kgms , 1 )
351+ kgm := kgms [constant .DefaultKeyspaceGroupID ]
352+ re .NotNil (kgm )
353+ re .Len (kgm .Member .ListenUrls , 1 )
354+ respListenURL := kgm .Member .ListenUrls [0 ]
355+ re .Equal (follower .GetConfig ().GetListenAddr (), respListenURL )
356+ re .NotEqual (primary .GetConfig ().GetListenAddr (), respListenURL )
357+ re .False (kgm .IsPrimary )
358+ }
0 commit comments