|
1 | 1 | package module |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "context" |
5 | 6 | "encoding/json" |
6 | 7 | "fmt" |
| 8 | + "io" |
7 | 9 | "net/http" |
8 | 10 | "net/http/httptest" |
9 | 11 | "strings" |
@@ -501,3 +503,208 @@ func TestHTTPCallStep_OAuth2_ConcurrentFetch(t *testing.T) { |
501 | 503 | t.Errorf("expected exactly 1 token request via singleflight, got %d", n) |
502 | 504 | } |
503 | 505 | } |
| 506 | + |
| 507 | +// TestHTTPCallStep_BodyFrom_String verifies that body_from with a string value sends raw bytes |
| 508 | +// without JSON-encoding and without auto-setting Content-Type: application/json. |
| 509 | +func TestHTTPCallStep_BodyFrom_String(t *testing.T) { |
| 510 | + type captured struct { |
| 511 | + body []byte |
| 512 | + contentType string |
| 513 | + } |
| 514 | + ch := make(chan captured, 1) |
| 515 | + |
| 516 | + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 517 | + b, err := io.ReadAll(r.Body) |
| 518 | + if err != nil { |
| 519 | + t.Errorf("failed to read request body: %v", err) |
| 520 | + } |
| 521 | + ch <- captured{body: b, contentType: r.Header.Get("Content-Type")} |
| 522 | + w.WriteHeader(http.StatusOK) |
| 523 | + _, _ = w.Write([]byte(`{"ok":true}`)) |
| 524 | + })) |
| 525 | + defer srv.Close() |
| 526 | + |
| 527 | + factory := NewHTTPCallStepFactory() |
| 528 | + step, err := factory("body-from-string", map[string]any{ |
| 529 | + "url": srv.URL, |
| 530 | + "method": "POST", |
| 531 | + "body_from": "raw_payload", |
| 532 | + }, nil) |
| 533 | + if err != nil { |
| 534 | + t.Fatalf("factory error: %v", err) |
| 535 | + } |
| 536 | + step.(*HTTPCallStep).httpClient = srv.Client() |
| 537 | + |
| 538 | + pc := NewPipelineContext(nil, nil) |
| 539 | + pc.Current["raw_payload"] = `{"hello":"world"}` |
| 540 | + |
| 541 | + if _, err := step.Execute(context.Background(), pc); err != nil { |
| 542 | + t.Fatalf("execute error: %v", err) |
| 543 | + } |
| 544 | + |
| 545 | + got := <-ch |
| 546 | + if string(got.body) != `{"hello":"world"}` { |
| 547 | + t.Errorf("expected raw body %q, got %q", `{"hello":"world"}`, string(got.body)) |
| 548 | + } |
| 549 | + // Content-Type should NOT be auto-set to application/json for raw bodies |
| 550 | + if got.contentType == "application/json" { |
| 551 | + t.Errorf("expected Content-Type not to be application/json for body_from, got %q", got.contentType) |
| 552 | + } |
| 553 | +} |
| 554 | + |
| 555 | +// TestHTTPCallStep_BodyFrom_Bytes verifies that body_from with a []byte value sends raw bytes. |
| 556 | +func TestHTTPCallStep_BodyFrom_Bytes(t *testing.T) { |
| 557 | + ch := make(chan []byte, 1) |
| 558 | + |
| 559 | + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 560 | + b, err := io.ReadAll(r.Body) |
| 561 | + if err != nil { |
| 562 | + t.Errorf("failed to read request body: %v", err) |
| 563 | + } |
| 564 | + ch <- b |
| 565 | + w.WriteHeader(http.StatusOK) |
| 566 | + _, _ = w.Write([]byte(`{}`)) |
| 567 | + })) |
| 568 | + defer srv.Close() |
| 569 | + |
| 570 | + factory := NewHTTPCallStepFactory() |
| 571 | + step, err := factory("body-from-bytes", map[string]any{ |
| 572 | + "url": srv.URL, |
| 573 | + "method": "POST", |
| 574 | + "body_from": "raw_data", |
| 575 | + }, nil) |
| 576 | + if err != nil { |
| 577 | + t.Fatalf("factory error: %v", err) |
| 578 | + } |
| 579 | + step.(*HTTPCallStep).httpClient = srv.Client() |
| 580 | + |
| 581 | + pc := NewPipelineContext(nil, nil) |
| 582 | + pc.Current["raw_data"] = []byte("binary\x00data") |
| 583 | + |
| 584 | + if _, err := step.Execute(context.Background(), pc); err != nil { |
| 585 | + t.Fatalf("execute error: %v", err) |
| 586 | + } |
| 587 | + |
| 588 | + gotBody := <-ch |
| 589 | + if !bytes.Equal(gotBody, []byte("binary\x00data")) { |
| 590 | + t.Errorf("expected raw bytes, got %q", string(gotBody)) |
| 591 | + } |
| 592 | +} |
| 593 | + |
| 594 | +// TestHTTPCallStep_BodyFrom_StepOutput verifies that body_from can resolve from step outputs. |
| 595 | +func TestHTTPCallStep_BodyFrom_StepOutput(t *testing.T) { |
| 596 | + ch := make(chan []byte, 1) |
| 597 | + |
| 598 | + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 599 | + b, err := io.ReadAll(r.Body) |
| 600 | + if err != nil { |
| 601 | + t.Errorf("failed to read request body: %v", err) |
| 602 | + } |
| 603 | + ch <- b |
| 604 | + w.WriteHeader(http.StatusOK) |
| 605 | + _, _ = w.Write([]byte(`{}`)) |
| 606 | + })) |
| 607 | + defer srv.Close() |
| 608 | + |
| 609 | + factory := NewHTTPCallStepFactory() |
| 610 | + step, err := factory("body-from-step", map[string]any{ |
| 611 | + "url": srv.URL, |
| 612 | + "method": "POST", |
| 613 | + "body_from": "steps.parse.raw_body", |
| 614 | + }, nil) |
| 615 | + if err != nil { |
| 616 | + t.Fatalf("factory error: %v", err) |
| 617 | + } |
| 618 | + step.(*HTTPCallStep).httpClient = srv.Client() |
| 619 | + |
| 620 | + pc := NewPipelineContext(nil, nil) |
| 621 | + pc.StepOutputs["parse"] = map[string]any{ |
| 622 | + "raw_body": `{"event":"push"}`, |
| 623 | + } |
| 624 | + |
| 625 | + if _, err := step.Execute(context.Background(), pc); err != nil { |
| 626 | + t.Fatalf("execute error: %v", err) |
| 627 | + } |
| 628 | + |
| 629 | + gotBody := <-ch |
| 630 | + if string(gotBody) != `{"event":"push"}` { |
| 631 | + t.Errorf("expected raw body from step output, got %q", string(gotBody)) |
| 632 | + } |
| 633 | +} |
| 634 | + |
| 635 | +// TestHTTPCallStep_BodyFrom_ContentTypeOverride verifies that Content-Type set in headers |
| 636 | +// takes effect even with body_from. |
| 637 | +func TestHTTPCallStep_BodyFrom_ContentTypeOverride(t *testing.T) { |
| 638 | + ch := make(chan string, 1) |
| 639 | + |
| 640 | + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 641 | + ch <- r.Header.Get("Content-Type") |
| 642 | + w.WriteHeader(http.StatusOK) |
| 643 | + _, _ = w.Write([]byte(`{}`)) |
| 644 | + })) |
| 645 | + defer srv.Close() |
| 646 | + |
| 647 | + factory := NewHTTPCallStepFactory() |
| 648 | + step, err := factory("body-from-ct", map[string]any{ |
| 649 | + "url": srv.URL, |
| 650 | + "method": "POST", |
| 651 | + "body_from": "payload", |
| 652 | + "headers": map[string]any{ |
| 653 | + "Content-Type": "application/xml", |
| 654 | + }, |
| 655 | + }, nil) |
| 656 | + if err != nil { |
| 657 | + t.Fatalf("factory error: %v", err) |
| 658 | + } |
| 659 | + step.(*HTTPCallStep).httpClient = srv.Client() |
| 660 | + |
| 661 | + pc := NewPipelineContext(nil, nil) |
| 662 | + pc.Current["payload"] = `<root><item>1</item></root>` |
| 663 | + |
| 664 | + if _, err := step.Execute(context.Background(), pc); err != nil { |
| 665 | + t.Fatalf("execute error: %v", err) |
| 666 | + } |
| 667 | + |
| 668 | + gotCT := <-ch |
| 669 | + if gotCT != "application/xml" { |
| 670 | + t.Errorf("expected Content-Type application/xml, got %q", gotCT) |
| 671 | + } |
| 672 | +} |
| 673 | + |
| 674 | +// TestHTTPCallStep_BodyFrom_NilValue verifies that body_from with a missing path sends no body. |
| 675 | +func TestHTTPCallStep_BodyFrom_NilValue(t *testing.T) { |
| 676 | + ch := make(chan []byte, 1) |
| 677 | + |
| 678 | + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 679 | + b, err := io.ReadAll(r.Body) |
| 680 | + if err != nil { |
| 681 | + t.Errorf("failed to read request body: %v", err) |
| 682 | + } |
| 683 | + ch <- b |
| 684 | + w.WriteHeader(http.StatusOK) |
| 685 | + _, _ = w.Write([]byte(`{}`)) |
| 686 | + })) |
| 687 | + defer srv.Close() |
| 688 | + |
| 689 | + factory := NewHTTPCallStepFactory() |
| 690 | + step, err := factory("body-from-nil", map[string]any{ |
| 691 | + "url": srv.URL, |
| 692 | + "method": "POST", |
| 693 | + "body_from": "nonexistent.path", |
| 694 | + }, nil) |
| 695 | + if err != nil { |
| 696 | + t.Fatalf("factory error: %v", err) |
| 697 | + } |
| 698 | + step.(*HTTPCallStep).httpClient = srv.Client() |
| 699 | + |
| 700 | + pc := NewPipelineContext(nil, nil) |
| 701 | + |
| 702 | + if _, err := step.Execute(context.Background(), pc); err != nil { |
| 703 | + t.Fatalf("execute error: %v", err) |
| 704 | + } |
| 705 | + |
| 706 | + gotBody := <-ch |
| 707 | + if len(gotBody) != 0 { |
| 708 | + t.Errorf("expected empty body for nil body_from, got %q", string(gotBody)) |
| 709 | + } |
| 710 | +} |
0 commit comments