go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/client/coordinator/query_test.go (about) 1 // Copyright 2015 The LUCI 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 // http://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 15 package coordinator 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "testing" 22 23 "google.golang.org/grpc/codes" 24 "google.golang.org/grpc/status" 25 "google.golang.org/protobuf/proto" 26 "google.golang.org/protobuf/types/known/timestamppb" 27 28 "go.chromium.org/luci/common/clock/testclock" 29 "go.chromium.org/luci/common/testing/prpctest" 30 logdog "go.chromium.org/luci/logdog/api/endpoints/coordinator/logs/v1" 31 "go.chromium.org/luci/logdog/api/logpb" 32 33 . "github.com/smartystreets/goconvey/convey" 34 ) 35 36 type testQueryLogsService struct { 37 testLogsServiceBase 38 39 LR *logdog.QueryRequest 40 H func(*logdog.QueryRequest) (*logdog.QueryResponse, error) 41 } 42 43 func (s *testQueryLogsService) Query(c context.Context, req *logdog.QueryRequest) (*logdog.QueryResponse, error) { 44 s.LR = proto.Clone(req).(*logdog.QueryRequest) 45 if h := s.H; h != nil { 46 return s.H(req) 47 } 48 return nil, errors.New("not implemented") 49 } 50 51 func gen(name string, state *logdog.LogStreamState) *logdog.QueryResponse_Stream { 52 return &logdog.QueryResponse_Stream{ 53 Path: fmt.Sprintf("test/+/%s", name), 54 State: state, 55 Desc: &logpb.LogStreamDescriptor{ 56 Prefix: "test", 57 Name: name, 58 }, 59 } 60 } 61 62 func shouldHaveLogStreams(actual any, expected ...any) string { 63 a := actual.([]*LogStream) 64 65 aList := make([]string, len(a)) 66 for i, ls := range a { 67 aList[i] = string(ls.Path) 68 } 69 70 eList := make([]string, len(expected)) 71 for i, exp := range expected { 72 eList[i] = exp.(string) 73 } 74 75 return ShouldResemble(aList, eList) 76 } 77 78 func TestClientQuery(t *testing.T) { 79 t.Parallel() 80 81 Convey(`A testing Client`, t, func() { 82 now := testclock.TestTimeLocal 83 c := context.Background() 84 85 ts := prpctest.Server{} 86 svc := testQueryLogsService{} 87 logdog.RegisterLogsServer(&ts, &svc) 88 89 // Create a testing server and client. 90 ts.Start(c) 91 defer ts.Close() 92 93 prpcClient, err := ts.NewClient() 94 if err != nil { 95 panic(err) 96 } 97 client := Client{ 98 C: logdog.NewLogsPRPCClient(prpcClient), 99 } 100 101 Convey(`When making a query request`, func() { 102 const project = "myproj" 103 const path = "**/+/**" 104 q := QueryOptions{ 105 Tags: map[string]string{ 106 "foo": "bar", 107 "baz": "qux", 108 }, 109 ContentType: "application/text", 110 Purged: Both, 111 State: true, 112 } 113 114 st := logdog.LogStreamState{ 115 Created: timestamppb.New(now), 116 } 117 118 var results []*LogStream 119 accumulate := func(s *LogStream) bool { 120 results = append(results, s) 121 return true 122 } 123 124 Convey(`Can accumulate results across queries.`, func() { 125 // This handler will return a single query per request, as well as a 126 // non-empty Next pointer for the next query element. It progresses 127 // "a" => "b" => "final" => "". 128 svc.H = func(req *logdog.QueryRequest) (*logdog.QueryResponse, error) { 129 r := logdog.QueryResponse{ 130 Project: string(project), 131 } 132 133 switch req.Next { 134 case "": 135 r.Streams = append(r.Streams, gen("a", &st)) 136 r.Next = "b" 137 case "b": 138 r.Streams = append(r.Streams, gen("b", &st)) 139 r.Next = "final" 140 case "final": 141 r.Streams = append(r.Streams, gen("final", &st)) 142 default: 143 return nil, errors.New("invalid cursor") 144 } 145 return &r, nil 146 } 147 148 So(client.Query(c, project, path, q, accumulate), ShouldBeNil) 149 So(results, shouldHaveLogStreams, "test/+/a", "test/+/b", "test/+/final") 150 }) 151 152 Convey(`Will stop invoking the callback if it returns false.`, func() { 153 // This handler will return three query results, "a", "b", and "c". 154 svc.H = func(*logdog.QueryRequest) (*logdog.QueryResponse, error) { 155 return &logdog.QueryResponse{ 156 Streams: []*logdog.QueryResponse_Stream{ 157 gen("a", &st), 158 gen("b", &st), 159 gen("c", &st), 160 }, 161 Next: "infiniteloop", 162 }, nil 163 } 164 165 accumulate = func(s *LogStream) bool { 166 results = append(results, s) 167 return len(results) < 3 168 } 169 So(client.Query(c, project, path, q, accumulate), ShouldBeNil) 170 So(results, shouldHaveLogStreams, "test/+/a", "test/+/b", "test/+/c") 171 }) 172 173 Convey(`Will properly handle state and protobuf deserialization.`, func() { 174 svc.H = func(*logdog.QueryRequest) (*logdog.QueryResponse, error) { 175 return &logdog.QueryResponse{ 176 Streams: []*logdog.QueryResponse_Stream{ 177 gen("a", &logdog.LogStreamState{ 178 Created: timestamppb.New(now), 179 }), 180 }, 181 }, nil 182 } 183 184 So(client.Query(c, project, path, q, accumulate), ShouldBeNil) 185 So(results, shouldHaveLogStreams, "test/+/a") 186 So(results[0], ShouldResemble, &LogStream{ 187 Path: "test/+/a", 188 Desc: logpb.LogStreamDescriptor{Prefix: "test", Name: "a"}, 189 State: StreamState{ 190 Created: now.UTC(), 191 }, 192 }) 193 }) 194 195 Convey(`Can query for stream types`, func() { 196 svc.H = func(*logdog.QueryRequest) (*logdog.QueryResponse, error) { 197 return &logdog.QueryResponse{}, nil 198 } 199 200 Convey(`Text`, func() { 201 q.StreamType = Text 202 So(client.Query(c, project, path, q, accumulate), ShouldBeNil) 203 So(svc.LR.StreamType, ShouldResemble, &logdog.QueryRequest_StreamTypeFilter{Value: logpb.StreamType_TEXT}) 204 }) 205 206 Convey(`Binary`, func() { 207 q.StreamType = Binary 208 So(client.Query(c, project, path, q, accumulate), ShouldBeNil) 209 So(svc.LR.StreamType, ShouldResemble, &logdog.QueryRequest_StreamTypeFilter{Value: logpb.StreamType_BINARY}) 210 }) 211 212 Convey(`Datagram`, func() { 213 q.StreamType = Datagram 214 So(client.Query(c, project, path, q, accumulate), ShouldBeNil) 215 So(svc.LR.StreamType, ShouldResemble, &logdog.QueryRequest_StreamTypeFilter{Value: logpb.StreamType_DATAGRAM}) 216 }) 217 }) 218 219 Convey(`Will return ErrNoAccess if unauthenticated.`, func() { 220 svc.H = func(*logdog.QueryRequest) (*logdog.QueryResponse, error) { 221 return nil, status.Error(codes.Unauthenticated, "unauthenticated") 222 } 223 224 So(client.Query(c, project, path, q, accumulate), ShouldEqual, ErrNoAccess) 225 }) 226 227 Convey(`Will return ErrNoAccess if permission denied.`, func() { 228 svc.H = func(*logdog.QueryRequest) (*logdog.QueryResponse, error) { 229 return nil, status.Error(codes.Unauthenticated, "unauthenticated") 230 } 231 232 So(client.Query(c, project, path, q, accumulate), ShouldEqual, ErrNoAccess) 233 }) 234 }) 235 }) 236 }