@@ -16,10 +16,84 @@ import (
1616 "google.golang.org/grpc"
1717 "google.golang.org/grpc/codes"
1818 "google.golang.org/grpc/credentials/insecure"
19+ "google.golang.org/grpc/metadata"
1920 "google.golang.org/grpc/status"
2021 "google.golang.org/grpc/test/bufconn"
2122)
2223
24+ type erringDeploymentsStream struct {
25+ ctx context.Context
26+ }
27+
28+ func (s * erringDeploymentsStream ) Send (* pb.DeploymentRequest ) error {
29+ return status .Error (codes .Internal , "stream terminated by RST_STREAM with error code: INTERNAL_ERROR" )
30+ }
31+
32+ func (s * erringDeploymentsStream ) Context () context.Context { return s .ctx }
33+
34+ func (s * erringDeploymentsStream ) SetHeader (metadata.MD ) error { return nil }
35+
36+ func (s * erringDeploymentsStream ) SendHeader (metadata.MD ) error { return nil }
37+
38+ func (s * erringDeploymentsStream ) SetTrailer (metadata.MD ) {}
39+
40+ func (s * erringDeploymentsStream ) SendMsg (any ) error { return nil }
41+
42+ func (s * erringDeploymentsStream ) RecvMsg (any ) error { return nil }
43+
44+ func TestDeploymentsUnregistersClusterWhenSendFails (t * testing.T ) {
45+ ctx , cancel := context .WithCancel (context .Background ())
46+ defer cancel ()
47+ _ , _ = telemetry .New (ctx , "test" , "" )
48+
49+ deploymentStore := database.MockDeploymentStore {}
50+ deploymentStore .On ("HistoricDeployments" , mock .Anything , mock .Anything , mock .Anything ).Return (nil , nil )
51+
52+ mockApiClients , _ := apiclient .NewMockClient (t )
53+ ds := New (& deploymentStore , mockApiClients .Deployments ()).(* dispatchServer )
54+
55+ stream := & erringDeploymentsStream {ctx : ctx }
56+ done := make (chan error , 1 )
57+ go func () {
58+ done <- ds .Deployments (& pb.GetDeploymentOpts {Cluster : "dev" , StartupTime : pb .TimeAsTimestamp (time .Now ())}, stream )
59+ }()
60+
61+ requireEventually (t , time .Second , func () bool {
62+ return len (ds .onlineClusters ()) == 1
63+ })
64+
65+ err := ds .SendDeploymentRequest (ctx , & pb.DeploymentRequest {Cluster : "dev" })
66+ if status .Code (err ) != codes .Internal {
67+ t .Fatalf ("expected send error to be propagated as Internal, got %v" , err )
68+ }
69+
70+ requireEventually (t , time .Second , func () bool {
71+ return len (ds .onlineClusters ()) == 0
72+ })
73+
74+ select {
75+ case err := <- done :
76+ if err == nil {
77+ t .Fatal ("expected Deployments to exit with send error" )
78+ }
79+ case <- time .After (time .Second ):
80+ t .Fatal ("Deployments did not exit after stream Send failed" )
81+ }
82+ }
83+
84+ func requireEventually (t * testing.T , timeout time.Duration , condition func () bool ) {
85+ t .Helper ()
86+
87+ deadline := time .Now ().Add (timeout )
88+ for time .Now ().Before (deadline ) {
89+ if condition () {
90+ return
91+ }
92+ time .Sleep (10 * time .Millisecond )
93+ }
94+ t .Fatal ("condition was not satisfied before timeout" )
95+ }
96+
2397func bufDialer (b * bufconn.Listener ) func (context.Context , string ) (net.Conn , error ) {
2498 return func (context.Context , string ) (net.Conn , error ) {
2599 return b .Dial ()
0 commit comments