internal/webserver_test_helper/webserver_test_helper.go (151 lines of code) (raw):

package webserver_test_helper //nolint:staticcheck import ( "bytes" "encoding/json" "errors" "fmt" "log/slog" "net/http" "net/url" "os" "os/exec" "path/filepath" "runtime" ) const BinaryName = "gitlab-zoekt" type WebserverHelper struct { indexDir string port int } type searchQuery struct { Q string `json:"q"` Opts searchOpts `json:"opts"` RepoIDs []uint32 `json:"repoIDs"` } type searchOpts struct { TotalMaxMatchCount int `json:"totalMaxMatchCount"` NumContextLines int `json:"numContextLines"` } type SearchResponse struct { Result searchResponseResult `json:"result"` } type searchResponseResult struct { FileCount int `json:"fileCount"` MatchCount int `json:"matchCount"` Files []searchResponseFiles `json:"files"` } type searchResponseFiles struct { FileName string `json:"fileName"` Branches []string `json:"branches"` } func (h *SearchResponse) FileNames() []string { files := make([]string, 0, len(h.Result.Files)) for _, f := range h.Result.Files { files = append(files, f.FileName) } return files } // findProjectDir returns the project root directory. // Since we know this file is in internal/webserver_test_helper, // we can simply go up two directories to find the project root. func findProjectDir() string { // Get the current file's directory _, filename, _, ok := runtime.Caller(0) if !ok { slog.Debug("Failed to get current file path") return "" } // From internal/webserver_test_helper, go up two directories to project root dir := filepath.Dir(filename) // dir of this file projectRoot, err := filepath.Abs(filepath.Join(dir, "../..")) // go up two levels if err != nil { slog.Debug("Failed to determine project root", "error", err) return "" } return projectRoot } func NewWebserverHelper(indexDir string, port int) *WebserverHelper { return &WebserverHelper{ indexDir: indexDir, port: port, } } func (h *WebserverHelper) listen() string { return fmt.Sprintf(":%d", h.port) } func (h *WebserverHelper) baseURL() string { return fmt.Sprintf("http://localhost:%d", h.port) } func (h *WebserverHelper) searchURL() string { res, err := url.JoinPath(h.baseURL(), "api", "search") if err != nil { panic(err) } return res } func (h *WebserverHelper) isReady() bool { res, err := http.Head(h.baseURL()) //nolint:bodyclose,noctx if err != nil { return false } return res.StatusCode == 200 } func (h *WebserverHelper) Start() (func(), error) { // First, try to find the binary in the project's bin directory projectDir := findProjectDir() binaryPath := BinaryName if projectDir != "" { candidatePath := fmt.Sprintf("%s/bin/%s", projectDir, BinaryName) if _, err := os.Stat(candidatePath); err == nil { binaryPath = candidatePath } } // If not found in project bin, check PATH if binaryPath == BinaryName { path, err := exec.LookPath(BinaryName) if err != nil { return func() {}, fmt.Errorf("binary %s not found in <PROJECT_DIR>/bin or PATH: %w", BinaryName, err) } binaryPath = path } cmd := exec.Command(binaryPath, "webserver", "-index_dir", h.indexDir, "-rpc", "-listen", h.listen()) //nolint:gosec cmd.Stdout = os.Stdout if err := cmd.Start(); err != nil { return func() {}, err } for !h.isReady() { } cancelFunc := func() { proc, err := os.FindProcess(cmd.Process.Pid) if err != nil { slog.Error("find process error", "err", err) } if err := proc.Kill(); err != nil { slog.Error("kill error", "err", err) } if err := cmd.Wait(); err != nil { slog.Error("wait() error", "err", err) } } return cancelFunc, nil } func (h *WebserverHelper) Search(repoID uint32, term string) (SearchResponse, error) { query := searchQuery{ Q: term, RepoIDs: []uint32{repoID}, Opts: searchOpts{ TotalMaxMatchCount: 20, NumContextLines: 0, }, } queryJSON, err := json.Marshal(query) if err != nil { return SearchResponse{}, err } res, err := http.Post(h.searchURL(), "application/json", bytes.NewBuffer(queryJSON)) //nolint:bodyclose,noctx if err != nil { return SearchResponse{}, err } if res.StatusCode != http.StatusOK { return SearchResponse{}, errors.New("search failed with non OK") } var result SearchResponse dec := json.NewDecoder(res.Body) if err := dec.Decode(&result); err != nil { return SearchResponse{}, err } return result, nil }