package api import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "browser.local/run/protocol" ) func TestPlatformClientLightweightChannelsCompleteWhileArtifactChunkIsBlocked(t *testing.T) { artifactStarted := make(chan struct{}) releaseArtifact := make(chan struct{}) artifactDone := make(chan struct{}) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/v1/run/artifacts/chunks": var request protocol.ArtifactChunkUploadRequest decodeTestRequest(t, r, &request) if request.ChunkIndex != 0 || len(request.Payload) == 0 { t.Fatalf("unexpected artifact payload: %+v", request) } close(artifactStarted) <-releaseArtifact writeTestJSON(t, w, protocol.ArtifactChunkUploadResponse{Accepted: true, TransferID: request.TransferID, ArtifactID: request.ArtifactID, ChunkIndex: request.ChunkIndex, ReceivedChunkIndexes: []int{0}, NextMissingChunkIndex: 1, ServerTime: fixedClientTestTime()}) close(artifactDone) case "/api/v1/run/control/heartbeat": var request protocol.RunHeartbeatRequest decodeTestRequest(t, r, &request) writeTestJSON(t, w, protocol.RunHeartbeatResponse{Accepted: true, RunEndpointID: request.RunEndpointID, NextHeartbeatSeconds: 15, ServerTime: fixedClientTestTime()}) case "/api/v1/run/jobs/result": var request protocol.RunJobResultRequest decodeTestRequest(t, r, &request) encoded, _ := json.Marshal(request) for _, forbidden := range []string{"payload", "entries", "/Users/", "unix://", "tcp://", "Bearer ", "sk-", "password="} { if strings.Contains(string(encoded), forbidden) { t.Fatalf("job result carried forbidden transfer content %q: %s", forbidden, string(encoded)) } } job := validRunJobAssignment() job.State = request.State job.ResultRef = request.ResultRef writeTestJSON(t, w, protocol.RunJobResultResponse{Accepted: true, Job: job, ServerTime: fixedClientTestTime()}) case "/api/v1/run/logs/batches": var request protocol.LogBatchIngestRequest decodeTestRequest(t, r, &request) if len(request.Entries) != 1 || request.FirstSeq != 1 || request.LastSeq != 1 { t.Fatalf("unexpected log batch: %+v", request) } writeTestJSON(t, w, protocol.LogBatchIngestResponse{Accepted: true, LogStreamID: request.LogStreamID, AcceptedFrom: 1, AcceptedTo: 1, LatestSeq: 1, ServerTime: fixedClientTestTime()}) default: t.Fatalf("unexpected request path %s", r.URL.Path) } })) defer server.Close() client, err := NewPlatformClient(server.URL) if err != nil { t.Fatalf("new client: %v", err) } errCh := make(chan error, 1) go func() { _, err := client.UploadArtifactChunk(context.Background(), validClientArtifactChunk()) errCh <- err }() select { case <-artifactStarted: case <-time.After(time.Second): t.Fatal("artifact request did not start") } lightCtx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if _, err := client.Heartbeat(lightCtx, validRunHeartbeatRequest("session-token")); err != nil { t.Fatalf("heartbeat should not wait for artifact chunk: %v", err) } if _, err := client.CompleteJob(lightCtx, validRunJobResultRequest()); err != nil { t.Fatalf("job result should not wait for artifact chunk: %v", err) } if _, err := client.IngestLogBatch(lightCtx, validClientLogBatch()); err != nil { t.Fatalf("log ingest should not wait for artifact chunk: %v", err) } select { case <-artifactDone: t.Fatal("artifact chunk completed before release") default: } close(releaseArtifact) select { case err := <-errCh: if err != nil { t.Fatalf("artifact chunk upload: %v", err) } case <-time.After(time.Second): t.Fatal("artifact chunk did not finish after release") } }