|
| 1 | +import { readFileSync } from "fs"; |
1 | 2 | import { TraceListener } from "./listener"; |
2 | 3 | import { ddtraceVersion, parentSpanFinishTimeHeader } from "./constants"; |
3 | 4 | import { datadogLambdaVersion } from "../constants"; |
@@ -698,7 +699,102 @@ describe("TraceListener", () => { |
698 | 699 | listener.onEndingInvocation(event, result, false); |
699 | 700 |
|
700 | 701 | expect(mockProcessAppsecResponse).toHaveBeenCalledTimes(1); |
701 | | - expect(mockProcessAppsecResponse).toHaveBeenCalledWith(mockSpan, result); |
| 702 | + // Non-HTTP trigger: there is no normalized status code to hand over. |
| 703 | + expect(mockProcessAppsecResponse).toHaveBeenCalledWith(mockSpan, result, undefined); |
| 704 | + } finally { |
| 705 | + currentSpanSpy.mockRestore(); |
| 706 | + } |
| 707 | + }); |
| 708 | + |
| 709 | + it("passes the normalized status code instead of the raw one for API Gateway v2", async () => { |
| 710 | + const mockSetTag = jest.fn(); |
| 711 | + const mockSpan = { setTag: mockSetTag }; |
| 712 | + const currentSpanSpy = jest.spyOn(TracerWrapper.prototype, "currentSpan", "get").mockReturnValue(mockSpan); |
| 713 | + |
| 714 | + try { |
| 715 | + const listener = new TraceListener(defaultConfig); |
| 716 | + const event = JSON.parse(readFileSync("./event_samples/api-gateway-v2.json", "utf8")); |
| 717 | + // API Gateway v2 defaults to 200 when the handler omits the status code, so the raw |
| 718 | + // result value (undefined) is not what AppSec should see. |
| 719 | + const result = { body: "ok" }; |
| 720 | + await listener.onStartInvocation(event, context as any); |
| 721 | + listener.onEndingInvocation(event, result, false); |
| 722 | + |
| 723 | + expect(mockProcessAppsecResponse).toHaveBeenCalledWith(mockSpan, result, "200"); |
| 724 | + } finally { |
| 725 | + currentSpanSpy.mockRestore(); |
| 726 | + } |
| 727 | + }); |
| 728 | + |
| 729 | + it("passes the normalized 502 when a buffered function returned no result", async () => { |
| 730 | + const mockSetTag = jest.fn(); |
| 731 | + const mockSpan = { setTag: mockSetTag }; |
| 732 | + const currentSpanSpy = jest.spyOn(TracerWrapper.prototype, "currentSpan", "get").mockReturnValue(mockSpan); |
| 733 | + |
| 734 | + try { |
| 735 | + const listener = new TraceListener(defaultConfig); |
| 736 | + const event = JSON.parse(readFileSync("./event_samples/application-load-balancer.json", "utf8")); |
| 737 | + await listener.onStartInvocation(event, context as any); |
| 738 | + listener.onEndingInvocation(event, undefined, false); |
| 739 | + |
| 740 | + expect(mockProcessAppsecResponse).toHaveBeenCalledWith(mockSpan, undefined, "502"); |
| 741 | + } finally { |
| 742 | + currentSpanSpy.mockRestore(); |
| 743 | + } |
| 744 | + }); |
| 745 | + |
| 746 | + it("passes the normalized 200 when a streaming function returned no result", async () => { |
| 747 | + const mockSetTag = jest.fn(); |
| 748 | + const mockSpan = { setTag: mockSetTag }; |
| 749 | + const currentSpanSpy = jest.spyOn(TracerWrapper.prototype, "currentSpan", "get").mockReturnValue(mockSpan); |
| 750 | + |
| 751 | + try { |
| 752 | + const listener = new TraceListener(defaultConfig); |
| 753 | + const event = JSON.parse(readFileSync("./event_samples/api-gateway-v2.json", "utf8")); |
| 754 | + await listener.onStartInvocation(event, context as any); |
| 755 | + listener.onEndingInvocation(event, undefined, true); |
| 756 | + |
| 757 | + expect(mockProcessAppsecResponse).toHaveBeenCalledWith(mockSpan, undefined, "200"); |
| 758 | + } finally { |
| 759 | + currentSpanSpy.mockRestore(); |
| 760 | + } |
| 761 | + }); |
| 762 | + |
| 763 | + it("tags http.status_code on the span before calling processAppsecResponse", async () => { |
| 764 | + const callOrder: string[] = []; |
| 765 | + mockProcessAppsecResponse.mockImplementation(() => callOrder.push("appsec")); |
| 766 | + |
| 767 | + const mockSetTag = jest.fn((key: string) => { |
| 768 | + if (key === "http.status_code") callOrder.push("tag"); |
| 769 | + }); |
| 770 | + const mockSpan = { setTag: mockSetTag }; |
| 771 | + const currentSpanSpy = jest.spyOn(TracerWrapper.prototype, "currentSpan", "get").mockReturnValue(mockSpan); |
| 772 | + |
| 773 | + try { |
| 774 | + const listener = new TraceListener(defaultConfig); |
| 775 | + const event = JSON.parse(readFileSync("./event_samples/api-gateway-v2.json", "utf8")); |
| 776 | + await listener.onStartInvocation(event, context as any); |
| 777 | + listener.onEndingInvocation(event, { statusCode: 201 }, false); |
| 778 | + |
| 779 | + expect(callOrder).toEqual(["tag", "appsec"]); |
| 780 | + } finally { |
| 781 | + currentSpanSpy.mockRestore(); |
| 782 | + } |
| 783 | + }); |
| 784 | + |
| 785 | + it("still calls processAppsecResponse on a 5xx response that short-circuits onEndingInvocation", async () => { |
| 786 | + const mockSetTag = jest.fn(); |
| 787 | + const mockSpan = { setTag: mockSetTag }; |
| 788 | + const currentSpanSpy = jest.spyOn(TracerWrapper.prototype, "currentSpan", "get").mockReturnValue(mockSpan); |
| 789 | + |
| 790 | + try { |
| 791 | + const listener = new TraceListener(defaultConfig); |
| 792 | + const event = JSON.parse(readFileSync("./event_samples/api-gateway-v2.json", "utf8")); |
| 793 | + await listener.onStartInvocation(event, context as any); |
| 794 | + const responseIs5xxError = listener.onEndingInvocation(event, { statusCode: 500 }, false); |
| 795 | + |
| 796 | + expect(responseIs5xxError).toBe(true); |
| 797 | + expect(mockProcessAppsecResponse).toHaveBeenCalledWith(mockSpan, { statusCode: 500 }, "500"); |
702 | 798 | } finally { |
703 | 799 | currentSpanSpy.mockRestore(); |
704 | 800 | } |
|
0 commit comments