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

     1  // Copyright 2018 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 receiver
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"strings"
    13  	"time"
    14  
    15  	mapset "github.com/deckarep/golang-set"
    16  	"github.com/web-platform-tests/wpt.fyi/api/checks"
    17  	"github.com/web-platform-tests/wpt.fyi/shared"
    18  )
    19  
    20  // InternalUsername is a special uploader whose password is kept secret and can
    21  // only be accessed by services in this AppEngine project via Datastore.
    22  const InternalUsername = "_processor"
    23  
    24  // HandleResultsCreate handles the POST requests for creating test runs.
    25  func HandleResultsCreate(a API, s checks.API, w http.ResponseWriter, r *http.Request) {
    26  	logger := shared.GetLogger(a.Context())
    27  
    28  	if AuthenticateUploader(a, r) != InternalUsername {
    29  		http.Error(w, "This is a private API.", http.StatusUnauthorized)
    30  
    31  		return
    32  	}
    33  	body, err := io.ReadAll(r.Body)
    34  	if err != nil {
    35  		http.Error(w, err.Error(), http.StatusInternalServerError)
    36  
    37  		return
    38  	}
    39  
    40  	var testRun shared.TestRun
    41  	if err := json.Unmarshal(body, &testRun); err != nil {
    42  		http.Error(w, "Failed to parse JSON: "+err.Error(), http.StatusBadRequest)
    43  
    44  		return
    45  	}
    46  
    47  	if testRun.TimeStart.IsZero() {
    48  		testRun.TimeStart = time.Now()
    49  	}
    50  	if testRun.TimeEnd.IsZero() {
    51  		testRun.TimeEnd = testRun.TimeStart
    52  	}
    53  	testRun.CreatedAt = time.Now()
    54  
    55  	// nolint:staticcheck // TODO: Fix staticcheck lint error (SA1019).
    56  	if len(testRun.FullRevisionHash) != 40 {
    57  		http.Error(w, "full_revision_hash must be the full SHA (40 chars)", http.StatusBadRequest)
    58  
    59  		return
    60  	} else if testRun.Revision != "" && strings.Index(testRun.FullRevisionHash, testRun.Revision) != 0 {
    61  		http.Error(w,
    62  			fmt.Sprintf(
    63  				"Mismatch of full_revision_hash and revision fields: %s vs %s",
    64  				testRun.FullRevisionHash,
    65  				testRun.Revision,
    66  			),
    67  			http.StatusBadRequest)
    68  
    69  		return
    70  	}
    71  	// nolint:staticcheck // TODO: Fix staticcheck lint error (SA1019).
    72  	testRun.Revision = testRun.FullRevisionHash[:10]
    73  
    74  	key, err := a.AddTestRun(&testRun)
    75  	if err != nil {
    76  		http.Error(w, err.Error(), http.StatusInternalServerError)
    77  
    78  		return
    79  	}
    80  	// Copy int64 representation of key into TestRun.ID so that clients can
    81  	// inspect/use key value.
    82  	testRun.ID = key.IntID()
    83  
    84  	// Do not schedule on pr_base to avoid redundancy with pr_head.
    85  	if !testRun.LabelsSet().Contains(shared.PRBaseLabel) {
    86  		spec := shared.ProductSpec{} // nolint:exhaustruct // TODO: Fix exhaustruct lint error
    87  		spec.BrowserName = testRun.BrowserName
    88  		spec.Labels = mapset.NewSet(testRun.Channel())
    89  		err = s.ScheduleResultsProcessing(testRun.FullRevisionHash, spec)
    90  		if err != nil {
    91  			logger.Warningf("Failed to schedule results: %s", err.Error())
    92  		}
    93  	}
    94  
    95  	// nolint:exhaustruct // TODO: Fix exhaustruct lint error.
    96  	pendingRun := shared.PendingTestRun{
    97  		ID:                testRun.ID,
    98  		Stage:             shared.StageValid,
    99  		ProductAtRevision: testRun.ProductAtRevision,
   100  	}
   101  	if err := a.UpdatePendingTestRun(pendingRun); err != nil {
   102  		// This is a non-fatal error; don't return.
   103  		logger.Errorf("Failed to update pending test run: %s", err.Error())
   104  	}
   105  
   106  	jsonOutput, err := json.Marshal(testRun)
   107  	if err != nil {
   108  		http.Error(w, err.Error(), http.StatusInternalServerError)
   109  
   110  		return
   111  	}
   112  	logger.Infof("Successfully created run %v (%s)", testRun.ID, testRun.String())
   113  	w.WriteHeader(http.StatusCreated)
   114  	_, err = w.Write(jsonOutput)
   115  	if err != nil {
   116  		logger.Warningf("Failed to write data in api/results/create handler: %s", err.Error())
   117  	}
   118  }