1package plugin
2
3import (
4 "io"
5 "net/http"
6 "net/http/httptest"
7 "strings"
8 "testing"
9
10 lua "github.com/yuin/gopher-lua"
11)
12
13// newTestManager creates a Manager with a fresh Lua VM for testing.
14func newTestManager() *Manager {
15 return NewManager()
16}
17
18func TestHTTPGet(t *testing.T) {
19 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20 if r.Method != http.MethodGet {
21 t.Errorf("expected GET, got %s", r.Method)
22 }
23 w.Header().Set("X-Test", "hello")
24 w.WriteHeader(http.StatusOK)
25 w.Write([]byte("ok"))
26 }))
27 defer srv.Close()
28
29 m := newTestManager()
30 defer m.Close()
31
32 err := m.state.DoString(`
33 local matcha = require("matcha")
34 res, err = matcha.http({ url = "` + srv.URL + `" })
35 `)
36 if err != nil {
37 t.Fatal(err)
38 }
39
40 errVal := m.state.GetGlobal("err")
41 if errVal != lua.LNil {
42 t.Fatalf("expected nil error, got %v", errVal)
43 }
44
45 res := m.state.GetGlobal("res")
46 tbl, ok := res.(*lua.LTable)
47 if !ok {
48 t.Fatalf("expected table, got %T", res)
49 }
50
51 status, ok := tbl.RawGetString("status").(lua.LNumber)
52 if !ok {
53 t.Fatalf("expected status to be LNumber, got %T", tbl.RawGetString("status"))
54 }
55 if status != 200 {
56 t.Errorf("expected status 200, got %v", status)
57 }
58 if body := tbl.RawGetString("body"); body.String() != "ok" {
59 t.Errorf("expected body 'ok', got %q", body.String())
60 }
61
62 headersVal := tbl.RawGetString("headers")
63 headers, ok := headersVal.(*lua.LTable)
64 if !ok {
65 t.Fatalf("expected headers to be LTable, got %T", headersVal)
66 }
67 if v := headers.RawGetString("x-test"); v.String() != "hello" {
68 t.Errorf("expected header x-test='hello', got %q", v.String())
69 }
70}
71
72func TestHTTPPostWithBodyAndHeaders(t *testing.T) {
73 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
74 if r.Method != http.MethodPost {
75 t.Errorf("expected POST, got %s", r.Method)
76 }
77 if ct := r.Header.Get("Content-Type"); ct != "application/json" {
78 t.Errorf("expected Content-Type application/json, got %q", ct)
79 }
80 body, _ := io.ReadAll(r.Body)
81 w.Write(body)
82 }))
83 defer srv.Close()
84
85 m := newTestManager()
86 defer m.Close()
87
88 err := m.state.DoString(`
89 local matcha = require("matcha")
90 res, err = matcha.http({
91 url = "` + srv.URL + `",
92 method = "post",
93 headers = { ["Content-Type"] = "application/json" },
94 body = '{"key":"value"}',
95 })
96 `)
97 if err != nil {
98 t.Fatal(err)
99 }
100
101 errVal := m.state.GetGlobal("err")
102 if errVal != lua.LNil {
103 t.Fatalf("expected nil error, got %v", errVal)
104 }
105
106 res := m.state.GetGlobal("res")
107 tbl, ok := res.(*lua.LTable)
108 if !ok {
109 t.Fatalf("expected table, got %T", res)
110 }
111 if body := tbl.RawGetString("body"); body.String() != `{"key":"value"}` {
112 t.Errorf("expected echoed body, got %q", body.String())
113 }
114}
115
116func TestHTTPMissingURL(t *testing.T) {
117 m := newTestManager()
118 defer m.Close()
119
120 err := m.state.DoString(`
121 local matcha = require("matcha")
122 res, err = matcha.http({})
123 `)
124 if err != nil {
125 t.Fatal(err)
126 }
127
128 resVal := m.state.GetGlobal("res")
129 if resVal != lua.LNil {
130 t.Errorf("expected nil result, got %v", resVal)
131 }
132
133 errVal := m.state.GetGlobal("err")
134 if errVal == lua.LNil {
135 t.Fatal("expected error, got nil")
136 }
137 if !strings.Contains(errVal.String(), "url") {
138 t.Errorf("expected error about url, got %q", errVal.String())
139 }
140}
141
142func TestHTTPInvalidScheme(t *testing.T) {
143 m := newTestManager()
144 defer m.Close()
145
146 err := m.state.DoString(`
147 local matcha = require("matcha")
148 res, err = matcha.http({ url = "file:///etc/passwd" })
149 `)
150 if err != nil {
151 t.Fatal(err)
152 }
153
154 resVal := m.state.GetGlobal("res")
155 if resVal != lua.LNil {
156 t.Errorf("expected nil result, got %v", resVal)
157 }
158
159 errVal := m.state.GetGlobal("err")
160 if errVal == lua.LNil {
161 t.Fatal("expected error, got nil")
162 }
163 if !strings.Contains(errVal.String(), "scheme") {
164 t.Errorf("expected error about scheme, got %q", errVal.String())
165 }
166}
167
168func TestHTTPBodyTruncation(t *testing.T) {
169 // Server returns more than 1 MB.
170 bigBody := strings.Repeat("x", httpMaxBodySize+1024)
171 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
172 w.Write([]byte(bigBody))
173 }))
174 defer srv.Close()
175
176 m := newTestManager()
177 defer m.Close()
178
179 err := m.state.DoString(`
180 local matcha = require("matcha")
181 res, err = matcha.http({ url = "` + srv.URL + `" })
182 body_len = #res.body
183 `)
184 if err != nil {
185 t.Fatal(err)
186 }
187
188 bodyLen := m.state.GetGlobal("body_len")
189 n, ok := bodyLen.(lua.LNumber)
190 if !ok {
191 t.Fatalf("expected number, got %T", bodyLen)
192 }
193 if int(n) > httpMaxBodySize {
194 t.Errorf("expected body to be capped at %d, got %d", httpMaxBodySize, int(n))
195 }
196}