github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/api/receiver/client/client.go (about)

     1  // Copyright 2019 The WPT Dashboard Project. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  // Package client is a package for simplifying the upload request made by a
     6  // client to the results receiver upload endpoint (/api/results/upload).
     7  package client
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"io"
    13  	"net/http"
    14  	"net/url"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/web-platform-tests/wpt.fyi/shared"
    19  )
    20  
    21  // UploadTimeout is the timeout to upload results to the results receiver.
    22  const UploadTimeout = time.Minute
    23  
    24  // Client is the interface for the client.
    25  type Client interface {
    26  	CreateRun(
    27  		sha,
    28  		username string,
    29  		password string,
    30  		resultURLs []string,
    31  		screenshotURLs []string,
    32  		labels []string) error
    33  }
    34  
    35  // NewClient returns a client impl.
    36  // nolint:ireturn // TODO: Fix ireturn lint error
    37  func NewClient(aeAPI shared.AppEngineAPI) Client {
    38  	return client{
    39  		aeAPI: aeAPI,
    40  	}
    41  }
    42  
    43  type client struct {
    44  	aeAPI shared.AppEngineAPI
    45  }
    46  
    47  // CreateRun issues a POST request to the results receiver with the given payload.
    48  func (c client) CreateRun(
    49  	sha,
    50  	username,
    51  	password string,
    52  	resultURLs []string,
    53  	screenshotURLs []string,
    54  	labels []string) error {
    55  	// https://github.com/web-platform-tests/wpt.fyi/blob/main/api/README.md#url-payload
    56  	payload := make(url.Values)
    57  	// Not to be confused with `revision` in the wpt.fyi TestRun model, this
    58  	// parameter is the full revision hash.
    59  	if sha != "" {
    60  		payload.Add("revision", sha)
    61  	}
    62  	for _, url := range resultURLs {
    63  		payload.Add("result_url", url)
    64  	}
    65  	for _, url := range screenshotURLs {
    66  		payload.Add("screenshot_url", url)
    67  	}
    68  	if labels != nil {
    69  		payload.Add("labels", strings.Join(labels, ","))
    70  	}
    71  	// Ensure we call back to this appengine version instance.
    72  	host := c.aeAPI.GetVersionedHostname()
    73  	payload.Add("callback_url", fmt.Sprintf("https://%s/api/results/create", host))
    74  
    75  	uploadURL := c.aeAPI.GetResultsUploadURL()
    76  	req, err := http.NewRequestWithContext(
    77  		context.Background(),
    78  		http.MethodPost,
    79  		uploadURL.String(),
    80  		strings.NewReader(payload.Encode()),
    81  	)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	req.SetBasicAuth(username, password)
    86  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    87  
    88  	hc := c.aeAPI.GetHTTPClientWithTimeout(UploadTimeout)
    89  	resp, err := hc.Do(req)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	respBody, err := io.ReadAll(resp.Body)
    94  	resp.Body.Close()
    95  	if err != nil {
    96  		return err
    97  	}
    98  	if resp.StatusCode >= 300 {
    99  		return fmt.Errorf("API error: HTTP %v: %s", resp.StatusCode, string(respBody))
   100  	}
   101  
   102  	return nil
   103  }