sdk/artifact/e2e_test.go
Louis Seubert 0f40c0b375
All checks were successful
default / ensure tests work (push) Successful in 28s
feat: artifacts client
2026-09-13 10:53:42 +02:00

114 lines
3.2 KiB
Go

package artifact
import (
"bytes"
"context"
"fmt"
"os"
"testing"
"time"
)
// TestE2E_ArtifactRoundTrip exercises the real results service. It only runs
// inside a CI pipeline (GITHUB_ACTIONS=true) and is skipped locally so that
// `go test ./...` stays hermetic. Its purpose is to detect drift between the
// results service the runner talks to and the API this client expects.
func TestE2E_ArtifactRoundTrip(t *testing.T) {
if os.Getenv("GITHUB_ACTIONS") != "true" {
t.Skip("e2e test only runs in CI")
}
resultsURL := os.Getenv("ACTIONS_RESULTS_URL")
runtimeToken := os.Getenv("ACTIONS_RUNTIME_TOKEN")
if resultsURL == "" || runtimeToken == "" {
t.Skip("ACTIONS_RESULTS_URL and ACTIONS_RUNTIME_TOKEN are required")
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
run, job, err := JobInfo()
if err != nil {
t.Fatalf("JobInfo: %v", err)
}
client := NewClient(resultsURL, runtimeToken)
name := fmt.Sprintf("e2e-%d-%d", os.Getpid(), time.Now().UnixNano())
expire := time.Now().Add(1 * time.Hour)
create, err := client.CreateArtifact(ctx, CreateArtifactRequest{
RunID: run,
JobRunID: job,
Name: name,
Version: 4,
ExpiresAt: &expire,
})
if err != nil {
t.Fatalf("CreateArtifact: %v", err)
}
if !create.Ok {
t.Fatalf("CreateArtifact not ok: %+v", create)
}
t.Cleanup(func() {
if _, err := client.DeleteArtifact(context.Background(), DeleteArtifactRequest{RunID: run, JobRunID: job, Name: name}); err != nil {
t.Logf("cleanup DeleteArtifact: %v", err)
}
})
content := []byte("artifact e2e " + name)
pushed, err := PushBlob(ctx, nil, bytes.NewReader(content), create.SignedUploadUrl, 0, 0)
if err != nil {
t.Fatalf("PushBlob: %v", err)
}
finalize, err := client.FinalizeArtifact(ctx, FinalizeArtifactRequest{
RunID: run,
JobRunID: job,
Name: name,
Size: pushed.Size,
Hash: pushed.SHA256Sum,
})
if err != nil {
t.Fatalf("FinalizeArtifact: %v", err)
}
if !finalize.Ok {
t.Fatalf("FinalizeArtifact not ok: %+v", finalize)
}
signed := getSignedArtifactURL(t, ctx, client, run, job, name)
var buf bytes.Buffer
pulled, err := PullBlob(ctx, nil, &buf, signed.SignedUrl)
if err != nil {
t.Fatalf("PullBlob: %v", err)
}
if !bytes.Equal(buf.Bytes(), content) {
t.Errorf("round trip mismatch: got %q want %q", buf.String(), content)
}
if pulled.SHA256Sum != pushed.SHA256Sum {
t.Errorf("hash mismatch: got %q want %q", pulled.SHA256Sum, pushed.SHA256Sum)
}
}
// getSignedArtifactURL retries briefly because a freshly finalized artifact may
// not be signed yet.
func getSignedArtifactURL(t *testing.T, ctx context.Context, client *Client, run, job, name string) *GetSignedArtifactURLResponse {
t.Helper()
var lastErr error
for attempt := 0; attempt < 5; attempt++ {
signed, err := client.GetSignedArtifactURL(ctx, GetSignedArtifactURLRequest{RunID: run, JobRunID: job, Name: name})
if err == nil && signed.SignedUrl != "" {
return signed
}
lastErr = err
select {
case <-ctx.Done():
t.Fatalf("GetSignedArtifactURL: %v", ctx.Err())
case <-time.After(time.Duration(attempt+1) * time.Second):
}
}
t.Fatalf("GetSignedArtifactURL: %v", lastErr)
return nil
}