query: set -target to DCS by default
[debiancodesearch.git] / endtoend_test.go
blob20285c81781ef1c71757f837833a0238ba95cf9a
1 package endtoend_test
3 import (
4 "context"
5 "encoding/json"
6 "flag"
7 "io"
8 "io/ioutil"
9 "net/http"
10 "path/filepath"
11 "reflect"
12 "strings"
13 "testing"
15 "google.golang.org/grpc"
16 "google.golang.org/grpc/codes"
17 "google.golang.org/grpc/status"
19 "github.com/Debian/dcs/grpcutil"
20 "github.com/Debian/dcs/internal/api"
21 "github.com/Debian/dcs/internal/localdcs"
22 "github.com/Debian/dcs/internal/proto/dcspb"
23 "github.com/google/go-cmp/cmp"
24 "google.golang.org/protobuf/encoding/prototext"
25 "google.golang.org/protobuf/proto"
28 func TestEndToEnd(t *testing.T) {
29 temp, err := ioutil.TempDir("", "dcs-endtoend")
30 if err != nil {
31 t.Fatal(err)
33 // TODO: refactor localdcs.Start to take options
34 flag.Set("localdcs_path", temp)
35 flag.Set("shard_path", filepath.Join(temp, "shard"))
36 flag.Set("shard_path", filepath.Join(temp, "shard"))
38 instance, err := localdcs.Start(
39 "-securecookie_hash_key=3270b4d09abccbf3fe59b957b1d429c8c58ac5def079ea4b245f66ade65168c2",
40 "-securecookie_block_key=cdba47f8f82be74175a75ec864aca56d8dcdc5610c88af446005766c6f9e6fd5",
42 if err != nil {
43 t.Fatal(err)
45 defer func() {
46 flag.Set("stop", "true")
47 localdcs.Start()
48 }()
50 // Created using dcs apikey-create; subject is set to “unittest!”
51 const apikey = "MTYxNDAxMzI4OXwyb2pFeXdGd0Q0VmdhTkZtMkRoeDdsa1JUa3ZwOTQtM3M2MG1ybGFWRkhacUZwZ1dmMmFlMG5lbkM3UWQ1SV96LXc9PXxdMAS04xLgPDL02_RXt7IftcfPZ4x839RuMhy0_WSX0g=="
53 t.Run("GRPC", func(t *testing.T) {
55 conn, err := grpcutil.DialTLS(instance.Addr,
56 filepath.Join(temp, "cert.pem"),
57 filepath.Join(temp, "key.pem"),
58 grpc.WithBlock())
59 if err != nil {
60 t.Fatalf("could not connect to %q: %v", instance.Addr, err)
62 defer conn.Close()
63 dcs := dcspb.NewDCSClient(conn)
66 // Verify API key are required:
67 stream, err := dcs.Search(context.Background(), &dcspb.SearchRequest{
68 Query: "i3Font",
70 if err != nil && status.Code(err) != codes.Unauthenticated {
71 t.Fatalf("Search(without API key) = %v, want Unauthenticated", err)
73 if _, err := stream.Recv(); err == nil || status.Code(err) != codes.Unauthenticated {
74 t.Fatalf("Search(without API key) = %v, want Unauthenticated", err)
78 stream, err := dcs.Search(context.Background(), &dcspb.SearchRequest{
79 Query: "i3Font",
80 Apikey: apikey,
82 if err != nil {
83 t.Fatal(err)
85 var events []*dcspb.Event
86 for {
87 event, err := stream.Recv()
88 if err == io.EOF {
89 break
91 if err != nil {
92 t.Fatal(err)
94 if prog, ok := event.Data.(*dcspb.Event_Progress); !ok {
95 continue // TODO: compare the rest, too
96 } else {
97 if prog.Progress.FilesProcessed > 0 &&
98 prog.Progress.FilesProcessed < prog.Progress.FilesTotal {
99 continue // TODO: compare intermediate progress updates, too
102 events = append(events, event)
104 t.Logf("%d events", len(events))
105 for idx, ev := range events {
106 t.Logf("event %d: %+v", idx, ev)
108 var queryId string
109 last := events[len(events)-1]
110 if p, ok := last.Data.(*dcspb.Event_Progress); ok {
111 queryId = p.Progress.QueryId
114 want := []*dcspb.Event{
115 &dcspb.Event{
116 Data: &dcspb.Event_Progress{
117 Progress: &dcspb.Progress{
118 QueryId: queryId,
119 FilesTotal: 17,
124 &dcspb.Event{
125 Data: &dcspb.Event_Progress{
126 Progress: &dcspb.Progress{
127 QueryId: queryId,
128 FilesProcessed: 17,
129 FilesTotal: 17,
130 // Results: 17,
136 t.Logf("printing %d events:", len(events))
137 for _, ev := range events {
138 t.Logf("event: %s", prototext.Format(ev))
139 // TODO: figure out why results is sometimes 17, sometimes less?!
140 // might be related to positional index
141 ev.Data.(*dcspb.Event_Progress).Progress.Results = 0
144 diff1 := cmp.Diff(want, events, cmp.Comparer(proto.Equal))
145 // The second event (progress update) obsoletes the first one. Depending
146 // on timing, only the second one may be received.
147 diff2 := cmp.Diff(want[1:], events, cmp.Comparer(proto.Equal))
148 if diff1 != "" && diff2 != "" {
149 t.Fatalf("Search: events differ (-want +got)\n%s", diff1)
153 t.Run("OpenAPI", func(t *testing.T) {
154 urlPrefix := "https://" + instance.Addr + "/api"
156 t.Run("OPTIONS", func(t *testing.T) {
157 req, err := http.NewRequest("OPTIONS", urlPrefix+"/v1/search", nil)
158 if err != nil {
159 t.Fatal(err)
161 resp, err := instance.HTTPClient.Do(req)
162 if err != nil {
163 t.Fatal(err)
165 if got, want := resp.StatusCode, http.StatusNoContent; got != want {
166 b, _ := ioutil.ReadAll(resp.Body)
167 t.Fatalf("unexpected HTTP status code: got %v (%s), want %v",
168 resp.Status,
169 strings.TrimSpace(string(b)),
170 want)
172 // TODO: verify CORS headers are present
175 t.Run("WithoutKey", func(t *testing.T) {
176 req, err := http.NewRequest("GET", urlPrefix+"/v1/search", nil)
177 if err != nil {
178 t.Fatal(err)
180 resp, err := instance.HTTPClient.Do(req)
181 if err != nil {
182 t.Fatal(err)
184 if got, want := resp.StatusCode, http.StatusForbidden; got != want {
185 b, _ := ioutil.ReadAll(resp.Body)
186 t.Fatalf("unexpected HTTP status code: got %v (%s), want %v",
187 resp.Status,
188 strings.TrimSpace(string(b)),
189 want)
193 t.Run("GET", func(t *testing.T) {
194 req, err := http.NewRequest("GET", urlPrefix+"/v1/search?query=i3Font", nil)
195 if err != nil {
196 t.Fatal(err)
198 req.Header.Set("x-dcs-apikey", apikey)
199 resp, err := instance.HTTPClient.Do(req)
200 if err != nil {
201 t.Fatal(err)
203 if got, want := resp.StatusCode, http.StatusOK; got != want {
204 b, _ := ioutil.ReadAll(resp.Body)
205 t.Fatalf("unexpected HTTP status code: got %v (%s), want %v",
206 resp.Status,
207 strings.TrimSpace(string(b)),
208 want)
211 var results []api.SearchResult
212 b, err := ioutil.ReadAll(resp.Body)
213 if err != nil {
214 t.Fatal(err)
216 if err := json.Unmarshal(b, &results); err != nil {
217 t.Fatal(err)
219 want := api.SearchResult{
220 Package: "i3-wm_4.5.1-2",
221 Path: "i3-wm_4.5.1-2/libi3/font.c",
222 Line: 0x8b,
223 Context: "i3Font load_font(const char *pattern, const bool fallback) {",
224 ContextBefore: []string{
225 " *",
226 " */",
228 ContextAfter: []string{
229 " i3Font font;",
230 " font.type = FONT_TYPE_NONE;",
233 for _, got := range results {
234 if reflect.DeepEqual(got, want) {
235 return // test passed
238 t.Fatalf("search result %+v not found in results %+v", want, results)
241 t.Run("PerPackage", func(t *testing.T) {
242 req, err := http.NewRequest("GET", urlPrefix+"/v1/searchperpackage?query=i3Font", nil)
243 if err != nil {
244 t.Fatal(err)
246 req.Header.Set("x-dcs-apikey", apikey)
247 resp, err := instance.HTTPClient.Do(req)
248 if err != nil {
249 t.Fatal(err)
251 if got, want := resp.StatusCode, http.StatusOK; got != want {
252 b, _ := ioutil.ReadAll(resp.Body)
253 t.Fatalf("unexpected HTTP status code: got %v (%s), want %v",
254 resp.Status,
255 strings.TrimSpace(string(b)),
256 want)
259 var results []api.PerPackageResult
260 b, err := ioutil.ReadAll(resp.Body)
261 if err != nil {
262 t.Fatal(err)
264 if err := json.Unmarshal(b, &results); err != nil {
265 t.Fatal(err)
267 if got, want := len(results), 1; got != want {
268 t.Fatalf("len(results) = %d, want %d", got, want)
270 for _, got := range results[0].Results {
271 if got.Path == "i3-wm_4.5.1-2/libi3/font.c" &&
272 strings.Contains(got.Context, "i3Font") {
273 return // test passed
276 t.Fatalf("search result i3-wm_4.5.1-2/libi3/font.c not found in results %+v", results)