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 }