Files
run/api/artifact_client_test.go
T
2026-08-26 09:56:43 +08:00

155 lines
6.5 KiB
Go

package api
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"browser.local/run/protocol"
)
func TestPlatformClientArtifactMethodsPostJSONAndDecodeResponses(t *testing.T) {
seen := map[string]bool{}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
seen[r.URL.Path] = true
switch r.URL.Path {
case "/api/v1/run/artifacts/open":
var request protocol.ArtifactTransferOpenRequest
decodeTestRequest(t, r, &request)
if request.ArtifactID != "artifact-1" || request.ChunkSizeBytes != 8 {
t.Fatalf("unexpected artifact open request: %+v", request)
}
writeTestJSON(t, w, validArtifactOpenResponse())
case "/api/v1/run/artifacts/chunks":
var request protocol.ArtifactChunkUploadRequest
decodeTestRequest(t, r, &request)
if request.TransferID != "transfer-1" || request.ChunkIndex != 0 || string(request.Payload) != "payload" {
t.Fatalf("unexpected artifact chunk request: %+v", request)
}
writeTestJSON(t, w, protocol.ArtifactChunkUploadResponse{Accepted: true, TransferID: "transfer-1", ArtifactID: "artifact-1", ChunkIndex: 0, ReceivedChunkIndexes: []int{0}, NextMissingChunkIndex: 1, ServerTime: fixedClientTestTime()})
case "/api/v1/run/artifacts/status":
var request protocol.ArtifactTransferStatusRequest
decodeTestRequest(t, r, &request)
if request.TransferID != "transfer-1" {
t.Fatalf("unexpected artifact status request: %+v", request)
}
writeTestJSON(t, w, protocol.ArtifactTransferStatusResponse{Accepted: true, TransferID: "transfer-1", ArtifactID: "artifact-1", Direction: "upload", TotalChunks: 2, ChunkSizeBytes: 8, ReceivedChunkIndexes: []int{0}, NextMissingChunkIndex: 1, ServerTime: fixedClientTestTime()})
case "/api/v1/run/artifacts/complete":
var request protocol.ArtifactTransferCompleteRequest
decodeTestRequest(t, r, &request)
if request.Checksum == "" || request.SizeBytes != 7 {
t.Fatalf("unexpected artifact complete request: %+v", request)
}
response := protocol.ArtifactTransferCompleteResponse{Accepted: true, TransferID: "transfer-1", Artifact: validArtifactMetadata("available"), Completed: true, ServerTime: fixedClientTestTime()}
writeTestJSON(t, w, response)
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)
}
ctx := context.Background()
open, err := client.OpenArtifactTransfer(ctx, validClientArtifactOpen())
if err != nil || !open.Accepted || open.TransferID != "transfer-1" {
t.Fatalf("open artifact response=%+v err=%v", open, err)
}
chunk, err := client.UploadArtifactChunk(ctx, validClientArtifactChunk())
if err != nil || chunk.NextMissingChunkIndex != 1 {
t.Fatalf("upload artifact chunk response=%+v err=%v", chunk, err)
}
status, err := client.QueryArtifactTransferStatus(ctx, validClientArtifactStatus())
if err != nil || len(status.ReceivedChunkIndexes) != 1 {
t.Fatalf("artifact status response=%+v err=%v", status, err)
}
complete, err := client.CompleteArtifactTransfer(ctx, validClientArtifactComplete())
if err != nil || !complete.Completed || complete.Artifact.State != "available" {
t.Fatalf("complete artifact response=%+v err=%v", complete, err)
}
for _, path := range []string{"/api/v1/run/artifacts/open", "/api/v1/run/artifacts/chunks", "/api/v1/run/artifacts/status", "/api/v1/run/artifacts/complete"} {
if !seen[path] {
t.Fatalf("expected request to %s", path)
}
}
}
func TestPlatformClientArtifactMethodReturnsErrorForPlatformFailure(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(`{"code":"validation_failed"}`))
}))
defer server.Close()
client, err := NewPlatformClient(server.URL)
if err != nil {
t.Fatalf("new client: %v", err)
}
if _, err := client.OpenArtifactTransfer(context.Background(), validClientArtifactOpen()); err == nil {
t.Fatal("expected platform error")
}
}
func TestArtifactChunkPayloadUsesJSONBase64Encoding(t *testing.T) {
encoded, err := json.Marshal(validClientArtifactChunk())
if err != nil {
t.Fatalf("marshal artifact chunk: %v", err)
}
if !json.Valid(encoded) || !containsJSONPayloadField(encoded) {
t.Fatalf("expected JSON encoded payload field, got %s", string(encoded))
}
}
func validClientArtifactOpen() protocol.ArtifactTransferOpenRequest {
return protocol.ArtifactTransferOpenRequest{
RunEndpointID: "run-local",
SessionToken: "session-token",
ArtifactID: "artifact-1",
Direction: "upload",
OwnerKind: "job",
OwnerID: "job-1",
SizeBytes: 7,
ChunkSizeBytes: 8,
Checksum: validSHA256Checksum(),
IdempotencyKey: "artifact-upload-1",
}
}
func validClientArtifactChunk() protocol.ArtifactChunkUploadRequest {
return protocol.ArtifactChunkUploadRequest{RunEndpointID: "run-local", SessionToken: "session-token", TransferID: "transfer-1", ArtifactID: "artifact-1", ChunkIndex: 0, Offset: 0, SizeBytes: 7, Checksum: validSHA256Checksum(), Payload: []byte("payload")}
}
func validClientArtifactStatus() protocol.ArtifactTransferStatusRequest {
return protocol.ArtifactTransferStatusRequest{RunEndpointID: "run-local", SessionToken: "session-token", TransferID: "transfer-1", ArtifactID: "artifact-1"}
}
func validClientArtifactComplete() protocol.ArtifactTransferCompleteRequest {
return protocol.ArtifactTransferCompleteRequest{RunEndpointID: "run-local", SessionToken: "session-token", TransferID: "transfer-1", ArtifactID: "artifact-1", Checksum: validSHA256Checksum(), SizeBytes: 7}
}
func validArtifactOpenResponse() protocol.ArtifactTransferOpenResponse {
return protocol.ArtifactTransferOpenResponse{Accepted: true, TransferID: "transfer-1", Direction: "upload", Artifact: validArtifactMetadata("uploading"), TotalChunks: 2, ChunkSizeBytes: 8, NextMissingChunkIndex: 0, ServerTime: fixedClientTestTime()}
}
func validArtifactMetadata(state string) protocol.ArtifactMetadata {
return protocol.ArtifactMetadata{ID: "artifact-1", OwnerKind: "job", OwnerID: "job-1", SizeBytes: 7, Checksum: validSHA256Checksum(), State: state, CreatedAt: fixedClientTestTime(), UpdatedAt: fixedClientTestTime()}
}
func validSHA256Checksum() string {
return "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
func containsJSONPayloadField(encoded []byte) bool {
var body map[string]any
if err := json.Unmarshal(encoded, &body); err != nil {
return false
}
_, exists := body["payload"]
return exists
}