1package cloudflare
2
3import (
4	"context"
5	"encoding/json"
6	"fmt"
7	"io/ioutil"
8	"net/http"
9	"regexp"
10	"strings"
11	"testing"
12	"time"
13
14	"github.com/stretchr/testify/assert"
15)
16
17const (
18	deleteWorkerResponseData = `{
19    "result": null,
20    "success": true,
21    "errors": [],
22    "messages": []
23}`
24	uploadWorkerResponseData = `{
25    "result": {
26        "script": "addEventListener('fetch', event => {\n    event.passThroughOnException()\nevent.respondWith(handleRequest(event.request))\n})\n\nasync function handleRequest(request) {\n    return fetch(request)\n}",
27        "etag": "279cf40d86d70b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43977a",
28        "size": 191,
29        "modified_on": "2018-06-09T15:17:01.989141Z"
30    },
31    "success": true,
32    "errors": [],
33    "messages": []
34}`
35	updateWorkerRouteResponse = `{
36    "result": {
37        "id": "e7a57d8746e74ae49c25994dadb421b1",
38        "pattern": "app3.example.com/*",
39        "enabled": true
40    },
41    "success": true,
42    "errors": [],
43    "messages": []
44}`
45	updateWorkerRouteEntResponse = `{
46    "result": {
47        "id": "e7a57d8746e74ae49c25994dadb421b1",
48        "pattern": "app3.example.com/*",
49        "script": "test_script_1"
50    },
51    "success": true,
52    "errors": [],
53    "messages": []
54}`
55	createWorkerRouteResponse = `{
56    "result": {
57        "id": "e7a57d8746e74ae49c25994dadb421b1"
58    },
59    "success": true,
60    "errors": [],
61    "messages": []
62}`
63	listRouteResponseData = `{
64    "result": [
65        {
66            "id": "e7a57d8746e74ae49c25994dadb421b1",
67            "pattern": "app1.example.com/*",
68            "enabled": true
69        },
70        {
71            "id": "f8b68e9857f85bf59c25994dadb421b1",
72            "pattern": "app2.example.com/*",
73            "enabled": false
74        }
75    ],
76    "success": true,
77    "errors": [],
78    "messages": []
79}`
80	listRouteEntResponseData = `{
81    "result": [
82        {
83            "id": "e7a57d8746e74ae49c25994dadb421b1",
84            "pattern": "app1.example.com/*",
85            "script": "test_script_1"
86        },
87        {
88            "id": "f8b68e9857f85bf59c25994dadb421b1",
89            "pattern": "app2.example.com/*",
90            "script": "test_script_2"
91        },
92        {
93            "id": "2b5bf4240cd34c77852fac70b1bf745a",
94            "pattern": "app3.example.com/*"
95        }
96    ],
97    "success": true,
98    "errors": [],
99    "messages": []
100}`
101	listBindingsResponseData = `{
102		"result": [
103			{
104				"name": "MY_KV",
105				"namespace_id": "89f5f8fd93f94cb98473f6f421aa3b65",
106				"type": "kv_namespace"
107			},
108			{
109				"name": "MY_WASM",
110				"type": "wasm_module"
111			},
112			{
113				"name": "MY_PLAIN_TEXT",
114				"type": "plain_text",
115				"text": "text"
116			},
117			{
118				"name": "MY_SECRET_TEXT",
119				"type": "secret_text"
120			},
121			{
122				"name": "MY_NEW_BINDING",
123				"type": "some_imaginary_new_binding_type"
124			}
125		],
126		"success": true,
127		"errors": [],
128		"messages": []
129	}`
130	listWorkersResponseData = `{
131  "result": [
132    {
133      "id": "bar",
134      "created_on": "2018-04-22T17:10:48.938097Z",
135      "modified_on": "2018-04-22T17:10:48.938097Z",
136      "etag": "279cf40d86d70b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43977a"
137    },
138    {
139      "id": "baz",
140      "created_on": "2018-04-22T17:10:48.938097Z",
141      "modified_on": "2018-04-22T17:10:48.938097Z",
142      "etag": "380dg51e97e80b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43088b"
143    }
144  ],
145  "success": true,
146  "errors": [],
147  "messages": []
148}`
149)
150
151var (
152	successResponse               = Response{Success: true, Errors: []ResponseInfo{}, Messages: []ResponseInfo{}}
153	workerScript                  = "addEventListener('fetch', event => {\n    event.passThroughOnException()\nevent.respondWith(handleRequest(event.request))\n})\n\nasync function handleRequest(request) {\n    return fetch(request)\n}"
154	deleteWorkerRouteResponseData = createWorkerRouteResponse
155	formDataContentTypeRegex      = regexp.MustCompile("^multipart/form-data; boundary=")
156)
157
158func getFormValue(r *http.Request, key string) ([]byte, error) {
159	err := r.ParseMultipartForm(1024 * 1024)
160	if err != nil {
161		return nil, err
162	}
163
164	// In Go 1.10 there was a bug where field values with a content-type
165	// but without a filename would end up in Form.File but in versions
166	// before and after 1.10 they would be in form.Value. Here we check
167	// both in order to handle both scenarios
168	// https://golang.org/doc/go1.11#mime/multipart
169
170	// pre/post v1.10
171	if values, ok := r.MultipartForm.Value[key]; ok {
172		return []byte(values[0]), nil
173	}
174
175	// v1.10
176	if fileHeaders, ok := r.MultipartForm.File[key]; ok {
177		file, err := fileHeaders[0].Open()
178		if err != nil {
179			return nil, err
180		}
181		return ioutil.ReadAll(file)
182	}
183
184	return nil, fmt.Errorf("no value found for key %v", key)
185}
186
187type multipartUpload = struct {
188	Script      string
189	BindingMeta map[string]workerBindingMeta
190}
191
192func parseMultipartUpload(r *http.Request) (multipartUpload, error) {
193	// Parse the metadata
194	mdBytes, err := getFormValue(r, "metadata")
195	if err != nil {
196		return multipartUpload{}, err
197	}
198
199	var metadata struct {
200		BodyPart string              `json:"body_part"`
201		Bindings []workerBindingMeta `json:"bindings"`
202	}
203	err = json.Unmarshal(mdBytes, &metadata)
204	if err != nil {
205		return multipartUpload{}, err
206	}
207
208	// Get the script
209	script, err := getFormValue(r, metadata.BodyPart)
210	if err != nil {
211		return multipartUpload{}, err
212	}
213
214	// Since bindings are specified in the Go API as a map but are uploaded as a
215	// JSON array, the ordering of uploaded bindings is non-deterministic. To make
216	// it easier to compare for equality without running into ordering issues, we
217	// convert it back to a map
218	bindingMeta := make(map[string]workerBindingMeta)
219	for _, binding := range metadata.Bindings {
220		bindingMeta[binding["name"].(string)] = binding
221	}
222
223	return multipartUpload{
224		Script:      string(script),
225		BindingMeta: bindingMeta,
226	}, nil
227}
228
229func TestWorkers_DeleteWorker(t *testing.T) {
230	setup()
231	defer teardown()
232
233	mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) {
234		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
235		w.Header().Set("content-type", "application/javascript")
236		fmt.Fprintf(w, deleteWorkerResponseData)
237	})
238	res, err := client.DeleteWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"})
239	want := WorkerScriptResponse{
240		successResponse,
241		WorkerScript{}}
242	if assert.NoError(t, err) {
243		assert.Equal(t, want.Response, res.Response)
244	}
245}
246
247func TestWorkers_DeleteWorkerWithName(t *testing.T) {
248	setup(UsingAccount("foo"))
249	defer teardown()
250
251	mux.HandleFunc("/accounts/foo/workers/scripts/bar", func(w http.ResponseWriter, r *http.Request) {
252		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
253		w.Header().Set("content-type", "application/javascript")
254		fmt.Fprintf(w, deleteWorkerResponseData)
255	})
256	res, err := client.DeleteWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"})
257	want := WorkerScriptResponse{
258		successResponse,
259		WorkerScript{}}
260	if assert.NoError(t, err) {
261		assert.Equal(t, want.Response, res.Response)
262	}
263}
264
265func TestWorkers_DeleteWorkerWithNameErrorsWithoutAccountId(t *testing.T) {
266	setup()
267	defer teardown()
268
269	_, err := client.DeleteWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"})
270	assert.Error(t, err)
271}
272
273func TestWorkers_DownloadWorker(t *testing.T) {
274	setup()
275	defer teardown()
276
277	mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) {
278		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
279		w.Header().Set("content-type", "application/javascript")
280		fmt.Fprintf(w, workerScript)
281	})
282	res, err := client.DownloadWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"})
283	want := WorkerScriptResponse{
284		successResponse,
285		WorkerScript{
286			Script: workerScript,
287		}}
288	if assert.NoError(t, err) {
289		assert.Equal(t, want.Script, res.Script)
290	}
291}
292
293func TestWorkers_DownloadWorkerWithName(t *testing.T) {
294	setup(UsingAccount("foo"))
295	defer teardown()
296
297	mux.HandleFunc("/accounts/foo/workers/scripts/bar", func(w http.ResponseWriter, r *http.Request) {
298		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
299		w.Header().Set("content-type", "application/javascript")
300		fmt.Fprintf(w, workerScript)
301	})
302	res, err := client.DownloadWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"})
303	want := WorkerScriptResponse{
304		successResponse,
305		WorkerScript{
306			Script: workerScript,
307		}}
308	if assert.NoError(t, err) {
309		assert.Equal(t, want.Script, res.Script)
310	}
311}
312
313func TestWorkers_DownloadWorkerWithNameErrorsWithoutAccountId(t *testing.T) {
314	setup()
315	defer teardown()
316
317	_, err := client.DownloadWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"})
318	assert.Error(t, err)
319}
320
321func TestWorkers_ListWorkerScripts(t *testing.T) {
322	setup(UsingAccount("foo"))
323	defer teardown()
324
325	mux.HandleFunc("/accounts/foo/workers/scripts", func(w http.ResponseWriter, r *http.Request) {
326		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
327		w.Header().Set("content-type", "application-json")
328		fmt.Fprintf(w, listWorkersResponseData)
329	})
330
331	res, err := client.ListWorkerScripts(context.Background())
332	sampleDate, _ := time.Parse(time.RFC3339Nano, "2018-04-22T17:10:48.938097Z")
333	want := []WorkerMetaData{
334		{
335			ID:         "bar",
336			ETAG:       "279cf40d86d70b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43977a",
337			CreatedOn:  sampleDate,
338			ModifiedOn: sampleDate,
339		},
340		{
341			ID:         "baz",
342			ETAG:       "380dg51e97e80b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43088b",
343			CreatedOn:  sampleDate,
344			ModifiedOn: sampleDate,
345		},
346	}
347	if assert.NoError(t, err) {
348		assert.Equal(t, want, res.WorkerList)
349	}
350}
351
352func TestWorkers_UploadWorker(t *testing.T) {
353	setup()
354	defer teardown()
355
356	mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) {
357		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
358		contentTypeHeader := r.Header.Get("content-type")
359		assert.Equal(t, "application/javascript", contentTypeHeader, "Expected content-type request header to be 'application/javascript', got %s", contentTypeHeader)
360		w.Header().Set("content-type", "application/json")
361		fmt.Fprintf(w, uploadWorkerResponseData)
362	})
363	res, err := client.UploadWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"}, workerScript)
364	formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z")
365	want := WorkerScriptResponse{
366		successResponse,
367		WorkerScript{
368			Script: workerScript,
369			WorkerMetaData: WorkerMetaData{
370				ETAG:       "279cf40d86d70b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43977a",
371				Size:       191,
372				ModifiedOn: formattedTime,
373			},
374		}}
375	if assert.NoError(t, err) {
376		assert.Equal(t, want, res)
377	}
378}
379
380func TestWorkers_UploadWorkerWithName(t *testing.T) {
381	setup(UsingAccount("foo"))
382	defer teardown()
383
384	mux.HandleFunc("/accounts/foo/workers/scripts/bar", func(w http.ResponseWriter, r *http.Request) {
385		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
386		contentTypeHeader := r.Header.Get("content-type")
387		assert.Equal(t, "application/javascript", contentTypeHeader, "Expected content-type request header to be 'application/javascript', got %s", contentTypeHeader)
388		w.Header().Set("content-type", "application/json")
389		fmt.Fprintf(w, uploadWorkerResponseData)
390	})
391	res, err := client.UploadWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, workerScript)
392	formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z")
393	want := WorkerScriptResponse{
394		successResponse,
395		WorkerScript{
396			Script: workerScript,
397			WorkerMetaData: WorkerMetaData{
398				ETAG:       "279cf40d86d70b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43977a",
399				Size:       191,
400				ModifiedOn: formattedTime,
401			},
402		}}
403	if assert.NoError(t, err) {
404		assert.Equal(t, want, res)
405	}
406}
407
408func TestWorkers_UploadWorkerSingleScriptWithAccount(t *testing.T) {
409	setup(UsingAccount("foo"))
410	defer teardown()
411
412	mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) {
413		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
414		contentTypeHeader := r.Header.Get("content-type")
415		assert.Equal(t, "application/javascript", contentTypeHeader, "Expected content-type request header to be 'application/javascript', got %s", contentTypeHeader)
416		w.Header().Set("content-type", "application/json")
417		fmt.Fprintf(w, uploadWorkerResponseData)
418	})
419	res, err := client.UploadWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"}, workerScript)
420	formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z")
421	want := WorkerScriptResponse{
422		successResponse,
423		WorkerScript{
424			Script: workerScript,
425			WorkerMetaData: WorkerMetaData{
426				ETAG:       "279cf40d86d70b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43977a",
427				Size:       191,
428				ModifiedOn: formattedTime,
429			},
430		}}
431	if assert.NoError(t, err) {
432		assert.Equal(t, want, res)
433	}
434}
435
436func TestWorkers_UploadWorkerWithNameErrorsWithoutAccountId(t *testing.T) {
437	setup()
438	defer teardown()
439
440	_, err := client.UploadWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, workerScript)
441	assert.Error(t, err)
442}
443
444func TestWorkers_UploadWorkerWithInheritBinding(t *testing.T) {
445	setup(UsingAccount("foo"))
446	defer teardown()
447
448	// Setup route handler for both single-script and multi-script
449	handler := func(w http.ResponseWriter, r *http.Request) {
450		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
451
452		mpUpload, err := parseMultipartUpload(r)
453		assert.NoError(t, err)
454
455		expectedBindings := map[string]workerBindingMeta{
456			"b1": {
457				"name": "b1",
458				"type": "inherit",
459			},
460			"b2": {
461				"name":     "b2",
462				"type":     "inherit",
463				"old_name": "old_binding_name",
464			},
465		}
466		assert.Equal(t, workerScript, mpUpload.Script)
467		assert.Equal(t, expectedBindings, mpUpload.BindingMeta)
468
469		w.Header().Set("content-type", "application/json")
470		fmt.Fprintf(w, uploadWorkerResponseData)
471	}
472	mux.HandleFunc("/zones/foo/workers/script", handler)
473	mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler)
474
475	scriptParams := WorkerScriptParams{
476		Script: workerScript,
477		Bindings: map[string]WorkerBinding{
478			"b1": WorkerInheritBinding{},
479			"b2": WorkerInheritBinding{
480				OldName: "old_binding_name",
481			},
482		},
483	}
484
485	// Expected response
486	formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z")
487	want := WorkerScriptResponse{
488		successResponse,
489		WorkerScript{
490			Script: workerScript,
491			WorkerMetaData: WorkerMetaData{
492				ETAG:       "279cf40d86d70b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43977a",
493				Size:       191,
494				ModifiedOn: formattedTime,
495			},
496		}}
497
498	// Test single-script
499	res, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ZoneID: "foo"}, &scriptParams)
500	if assert.NoError(t, err) {
501		assert.Equal(t, want, res)
502	}
503
504	// Test multi-script
505	res, err = client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams)
506	if assert.NoError(t, err) {
507		assert.Equal(t, want, res)
508	}
509}
510
511func TestWorkers_UploadWorkerWithKVBinding(t *testing.T) {
512	setup(UsingAccount("foo"))
513	defer teardown()
514
515	handler := func(w http.ResponseWriter, r *http.Request) {
516		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
517
518		mpUpload, err := parseMultipartUpload(r)
519		assert.NoError(t, err)
520
521		expectedBindings := map[string]workerBindingMeta{
522			"b1": {
523				"name":         "b1",
524				"type":         "kv_namespace",
525				"namespace_id": "test-namespace",
526			},
527		}
528		assert.Equal(t, workerScript, mpUpload.Script)
529		assert.Equal(t, expectedBindings, mpUpload.BindingMeta)
530
531		w.Header().Set("content-type", "application/json")
532		fmt.Fprintf(w, uploadWorkerResponseData)
533	}
534	mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler)
535
536	scriptParams := WorkerScriptParams{
537		Script: workerScript,
538		Bindings: map[string]WorkerBinding{
539			"b1": WorkerKvNamespaceBinding{
540				NamespaceID: "test-namespace",
541			},
542		},
543	}
544	_, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams)
545	assert.NoError(t, err)
546}
547
548func TestWorkers_UploadWorkerWithWasmBinding(t *testing.T) {
549	setup(UsingAccount("foo"))
550	defer teardown()
551
552	handler := func(w http.ResponseWriter, r *http.Request) {
553		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
554
555		mpUpload, err := parseMultipartUpload(r)
556		assert.NoError(t, err)
557
558		partName := mpUpload.BindingMeta["b1"]["part"].(string)
559		expectedBindings := map[string]workerBindingMeta{
560			"b1": {
561				"name": "b1",
562				"type": "wasm_module",
563				"part": partName,
564			},
565		}
566		assert.Equal(t, workerScript, mpUpload.Script)
567		assert.Equal(t, expectedBindings, mpUpload.BindingMeta)
568
569		wasmContent, err := getFormValue(r, partName)
570		assert.NoError(t, err)
571		assert.Equal(t, []byte("fake-wasm"), wasmContent)
572
573		w.Header().Set("content-type", "application/json")
574		fmt.Fprintf(w, uploadWorkerResponseData)
575	}
576	mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler)
577
578	scriptParams := WorkerScriptParams{
579		Script: workerScript,
580		Bindings: map[string]WorkerBinding{
581			"b1": WorkerWebAssemblyBinding{
582				Module: strings.NewReader("fake-wasm"),
583			},
584		},
585	}
586	_, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams)
587	assert.NoError(t, err)
588}
589
590func TestWorkers_UploadWorkerWithPlainTextBinding(t *testing.T) {
591	setup(UsingAccount("foo"))
592	defer teardown()
593
594	handler := func(w http.ResponseWriter, r *http.Request) {
595		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
596
597		mpUpload, err := parseMultipartUpload(r)
598		assert.NoError(t, err)
599
600		expectedBindings := map[string]workerBindingMeta{
601			"b1": {
602				"name": "b1",
603				"type": "plain_text",
604				"text": "plain text value",
605			},
606		}
607		assert.Equal(t, workerScript, mpUpload.Script)
608		assert.Equal(t, expectedBindings, mpUpload.BindingMeta)
609
610		w.Header().Set("content-type", "application/json")
611		fmt.Fprintf(w, uploadWorkerResponseData)
612	}
613	mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler)
614
615	scriptParams := WorkerScriptParams{
616		Script: workerScript,
617		Bindings: map[string]WorkerBinding{
618			"b1": WorkerPlainTextBinding{
619				Text: "plain text value",
620			},
621		},
622	}
623	_, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams)
624	assert.NoError(t, err)
625}
626
627func TestWorkers_UploadWorkerWithSecretTextBinding(t *testing.T) {
628	setup(UsingAccount("foo"))
629	defer teardown()
630
631	handler := func(w http.ResponseWriter, r *http.Request) {
632		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
633
634		mpUpload, err := parseMultipartUpload(r)
635		assert.NoError(t, err)
636
637		expectedBindings := map[string]workerBindingMeta{
638			"b1": {
639				"name": "b1",
640				"type": "secret_text",
641				"text": "secret text value",
642			},
643		}
644		assert.Equal(t, workerScript, mpUpload.Script)
645		assert.Equal(t, expectedBindings, mpUpload.BindingMeta)
646
647		w.Header().Set("content-type", "application/json")
648		fmt.Fprintf(w, uploadWorkerResponseData)
649	}
650	mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler)
651
652	scriptParams := WorkerScriptParams{
653		Script: workerScript,
654		Bindings: map[string]WorkerBinding{
655			"b1": WorkerSecretTextBinding{
656				Text: "secret text value",
657			},
658		},
659	}
660	_, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams)
661	assert.NoError(t, err)
662}
663
664func TestWorkers_CreateWorkerRoute(t *testing.T) {
665	setup()
666	defer teardown()
667
668	mux.HandleFunc("/zones/foo/workers/filters", func(w http.ResponseWriter, r *http.Request) {
669		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
670		w.Header().Set("content-type", "application-json")
671		fmt.Fprintf(w, createWorkerRouteResponse)
672	})
673	route := WorkerRoute{Pattern: "app1.example.com/*", Enabled: true}
674	res, err := client.CreateWorkerRoute(context.Background(), "foo", route)
675	want := WorkerRouteResponse{successResponse, WorkerRoute{ID: "e7a57d8746e74ae49c25994dadb421b1"}}
676	if assert.NoError(t, err) {
677		assert.Equal(t, want, res)
678	}
679}
680
681func TestWorkers_CreateWorkerRouteEnt(t *testing.T) {
682	setup(UsingAccount("foo"))
683	defer teardown()
684
685	mux.HandleFunc("/zones/foo/workers/routes", func(w http.ResponseWriter, r *http.Request) {
686		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
687		w.Header().Set("content-type", "application-json")
688		fmt.Fprintf(w, createWorkerRouteResponse)
689	})
690	route := WorkerRoute{Pattern: "app1.example.com/*", Script: "test_script"}
691	res, err := client.CreateWorkerRoute(context.Background(), "foo", route)
692	want := WorkerRouteResponse{successResponse, WorkerRoute{ID: "e7a57d8746e74ae49c25994dadb421b1"}}
693	if assert.NoError(t, err) {
694		assert.Equal(t, want, res)
695	}
696}
697
698func TestWorkers_CreateWorkerRouteSingleScriptWithAccount(t *testing.T) {
699	setup(UsingAccount("foo"))
700	defer teardown()
701
702	mux.HandleFunc("/zones/foo/workers/filters", func(w http.ResponseWriter, r *http.Request) {
703		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
704		w.Header().Set("content-type", "application-json")
705		fmt.Fprintf(w, createWorkerRouteResponse)
706	})
707	route := WorkerRoute{Pattern: "app1.example.com/*", Enabled: true}
708	res, err := client.CreateWorkerRoute(context.Background(), "foo", route)
709	want := WorkerRouteResponse{successResponse, WorkerRoute{ID: "e7a57d8746e74ae49c25994dadb421b1"}}
710	if assert.NoError(t, err) {
711		assert.Equal(t, want, res)
712	}
713}
714
715func TestWorkers_CreateWorkerRouteErrorsWhenMixingSingleAndMultiScriptProperties(t *testing.T) {
716	setup(UsingAccount("foo"))
717	defer teardown()
718
719	route := WorkerRoute{Pattern: "app1.example.com/*", Script: "test_script", Enabled: true}
720	_, err := client.CreateWorkerRoute(context.Background(), "foo", route)
721	assert.EqualError(t, err, "Only `Script` or `Enabled` may be specified for a WorkerRoute, not both")
722}
723
724func TestWorkers_CreateWorkerRouteWithNoScript(t *testing.T) {
725	setup(UsingAccount("foo"))
726
727	mux.HandleFunc("/zones/foo/workers/routes", func(w http.ResponseWriter, r *http.Request) {
728		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
729		w.Header().Set("content-type", "application-json")
730		fmt.Fprintf(w, createWorkerRouteResponse)
731	})
732
733	route := WorkerRoute{Pattern: "app1.example.com/*"}
734	_, err := client.CreateWorkerRoute(context.Background(), "foo", route)
735	assert.NoError(t, err)
736}
737
738func TestWorkers_DeleteWorkerRoute(t *testing.T) {
739	setup()
740	defer teardown()
741
742	mux.HandleFunc("/zones/foo/workers/routes/e7a57d8746e74ae49c25994dadb421b1", func(w http.ResponseWriter, r *http.Request) {
743		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
744		w.Header().Set("content-type", "application-json")
745		fmt.Fprintf(w, deleteWorkerRouteResponseData)
746	})
747	res, err := client.DeleteWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1")
748	want := WorkerRouteResponse{successResponse,
749		WorkerRoute{
750			ID: "e7a57d8746e74ae49c25994dadb421b1",
751		}}
752	if assert.NoError(t, err) {
753		assert.Equal(t, want, res)
754	}
755}
756
757func TestWorkers_DeleteWorkerRouteEnt(t *testing.T) {
758	setup(UsingAccount("foo"))
759	defer teardown()
760
761	mux.HandleFunc("/zones/foo/workers/routes/e7a57d8746e74ae49c25994dadb421b1", func(w http.ResponseWriter, r *http.Request) {
762		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
763		w.Header().Set("content-type", "application-json")
764		fmt.Fprintf(w, deleteWorkerRouteResponseData)
765	})
766	res, err := client.DeleteWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1")
767	want := WorkerRouteResponse{successResponse,
768		WorkerRoute{
769			ID: "e7a57d8746e74ae49c25994dadb421b1",
770		}}
771	if assert.NoError(t, err) {
772		assert.Equal(t, want, res)
773	}
774}
775
776func TestWorkers_ListWorkerRoutes(t *testing.T) {
777	setup()
778	defer teardown()
779
780	mux.HandleFunc("/zones/foo/workers/filters", func(w http.ResponseWriter, r *http.Request) {
781		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
782		w.Header().Set("content-type", "application-json")
783		fmt.Fprintf(w, listRouteResponseData)
784	})
785
786	res, err := client.ListWorkerRoutes(context.Background(), "foo")
787	want := WorkerRoutesResponse{successResponse,
788		[]WorkerRoute{
789			{ID: "e7a57d8746e74ae49c25994dadb421b1", Pattern: "app1.example.com/*", Enabled: true},
790			{ID: "f8b68e9857f85bf59c25994dadb421b1", Pattern: "app2.example.com/*", Enabled: false},
791		},
792	}
793	if assert.NoError(t, err) {
794		assert.Equal(t, want, res)
795	}
796}
797
798func TestWorkers_ListWorkerRoutesEnt(t *testing.T) {
799	setup(UsingAccount("foo"))
800	defer teardown()
801
802	mux.HandleFunc("/zones/foo/workers/routes", func(w http.ResponseWriter, r *http.Request) {
803		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
804		w.Header().Set("content-type", "application-json")
805		fmt.Fprintf(w, listRouteEntResponseData)
806	})
807
808	res, err := client.ListWorkerRoutes(context.Background(), "foo")
809	want := WorkerRoutesResponse{successResponse,
810		[]WorkerRoute{
811			{ID: "e7a57d8746e74ae49c25994dadb421b1", Pattern: "app1.example.com/*", Script: "test_script_1", Enabled: true},
812			{ID: "f8b68e9857f85bf59c25994dadb421b1", Pattern: "app2.example.com/*", Script: "test_script_2", Enabled: true},
813			{ID: "2b5bf4240cd34c77852fac70b1bf745a", Pattern: "app3.example.com/*", Script: "", Enabled: false},
814		},
815	}
816	if assert.NoError(t, err) {
817		assert.Equal(t, want, res)
818	}
819}
820
821func TestWorkers_UpdateWorkerRoute(t *testing.T) {
822	setup()
823	defer teardown()
824
825	mux.HandleFunc("/zones/foo/workers/filters/e7a57d8746e74ae49c25994dadb421b1", func(w http.ResponseWriter, r *http.Request) {
826		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
827		w.Header().Set("content-type", "application-json")
828		fmt.Fprintf(w, updateWorkerRouteResponse)
829	})
830	route := WorkerRoute{Pattern: "app3.example.com/*", Enabled: true}
831	res, err := client.UpdateWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1", route)
832	want := WorkerRouteResponse{successResponse,
833		WorkerRoute{
834			ID:      "e7a57d8746e74ae49c25994dadb421b1",
835			Pattern: "app3.example.com/*",
836			Enabled: true,
837		}}
838	if assert.NoError(t, err) {
839		assert.Equal(t, want, res)
840	}
841}
842
843func TestWorkers_UpdateWorkerRouteEnt(t *testing.T) {
844	setup(UsingAccount("foo"))
845	defer teardown()
846
847	mux.HandleFunc("/zones/foo/workers/routes/e7a57d8746e74ae49c25994dadb421b1", func(w http.ResponseWriter, r *http.Request) {
848		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
849		w.Header().Set("content-type", "application-json")
850		fmt.Fprintf(w, updateWorkerRouteEntResponse)
851	})
852	route := WorkerRoute{Pattern: "app3.example.com/*", Script: "test_script_1"}
853	res, err := client.UpdateWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1", route)
854	want := WorkerRouteResponse{successResponse,
855		WorkerRoute{
856			ID:      "e7a57d8746e74ae49c25994dadb421b1",
857			Pattern: "app3.example.com/*",
858			Script:  "test_script_1",
859		}}
860	if assert.NoError(t, err) {
861		assert.Equal(t, want, res)
862	}
863}
864
865func TestWorkers_UpdateWorkerRouteSingleScriptWithAccount(t *testing.T) {
866	setup(UsingAccount("foo"))
867	defer teardown()
868
869	mux.HandleFunc("/zones/foo/workers/filters/e7a57d8746e74ae49c25994dadb421b1", func(w http.ResponseWriter, r *http.Request) {
870		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
871		w.Header().Set("content-type", "application-json")
872		fmt.Fprintf(w, updateWorkerRouteEntResponse)
873	})
874	route := WorkerRoute{Pattern: "app3.example.com/*", Enabled: true}
875	res, err := client.UpdateWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1", route)
876	want := WorkerRouteResponse{successResponse,
877		WorkerRoute{
878			ID:      "e7a57d8746e74ae49c25994dadb421b1",
879			Pattern: "app3.example.com/*",
880			Script:  "test_script_1",
881		}}
882	if assert.NoError(t, err) {
883		assert.Equal(t, want, res)
884	}
885}
886
887func TestWorkers_ListWorkerBindingsMultiScript(t *testing.T) {
888	setup(UsingAccount("foo"))
889	defer teardown()
890
891	mux.HandleFunc("/accounts/foo/workers/scripts/my-script/bindings", func(w http.ResponseWriter, r *http.Request) {
892		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
893		w.Header().Set("content-type", "application-json")
894		fmt.Fprintf(w, listBindingsResponseData)
895	})
896
897	mux.HandleFunc("/accounts/foo/workers/scripts/my-script/bindings/MY_WASM/content", func(w http.ResponseWriter, r *http.Request) {
898		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
899		w.Header().Set("content-type", "application/wasm")
900		_, _ = w.Write([]byte("mock multi-script wasm"))
901	})
902
903	res, err := client.ListWorkerBindings(context.Background(), &WorkerRequestParams{
904		ScriptName: "my-script",
905	})
906	assert.NoError(t, err)
907
908	assert.Equal(t, successResponse, res.Response)
909	assert.Equal(t, 5, len(res.BindingList))
910
911	assert.Equal(t, res.BindingList[0], WorkerBindingListItem{
912		Name: "MY_KV",
913		Binding: WorkerKvNamespaceBinding{
914			NamespaceID: "89f5f8fd93f94cb98473f6f421aa3b65",
915		},
916	})
917	assert.Equal(t, WorkerKvNamespaceBindingType, res.BindingList[0].Binding.Type())
918
919	assert.Equal(t, "MY_WASM", res.BindingList[1].Name)
920	wasmBinding := res.BindingList[1].Binding.(WorkerWebAssemblyBinding)
921	wasmModuleContent, err := ioutil.ReadAll(wasmBinding.Module)
922	assert.NoError(t, err)
923	assert.Equal(t, []byte("mock multi-script wasm"), wasmModuleContent)
924	assert.Equal(t, WorkerWebAssemblyBindingType, res.BindingList[1].Binding.Type())
925
926	assert.Equal(t, res.BindingList[2], WorkerBindingListItem{
927		Name: "MY_PLAIN_TEXT",
928		Binding: WorkerPlainTextBinding{
929			Text: "text",
930		},
931	})
932	assert.Equal(t, WorkerPlainTextBindingType, res.BindingList[2].Binding.Type())
933
934	assert.Equal(t, res.BindingList[3], WorkerBindingListItem{
935		Name:    "MY_SECRET_TEXT",
936		Binding: WorkerSecretTextBinding{},
937	})
938	assert.Equal(t, WorkerSecretTextBindingType, res.BindingList[3].Binding.Type())
939
940	assert.Equal(t, res.BindingList[4], WorkerBindingListItem{
941		Name:    "MY_NEW_BINDING",
942		Binding: WorkerInheritBinding{},
943	})
944	assert.Equal(t, WorkerInheritBindingType, res.BindingList[4].Binding.Type())
945}
946
947func TestWorkers_UpdateWorkerRouteErrorsWhenMixingSingleAndMultiScriptProperties(t *testing.T) {
948	setup(UsingAccount("foo"))
949	defer teardown()
950
951	route := WorkerRoute{Pattern: "app1.example.com/*", Script: "test_script", Enabled: true}
952	_, err := client.UpdateWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1", route)
953	assert.EqualError(t, err, "Only `Script` or `Enabled` may be specified for a WorkerRoute, not both")
954}
955
956func TestWorkers_UpdateWorkerRouteWithNoScript(t *testing.T) {
957	setup(UsingAccount("foo"))
958
959	mux.HandleFunc("/zones/foo/workers/routes/e7a57d8746e74ae49c25994dadb421b1", func(w http.ResponseWriter, r *http.Request) {
960		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
961		w.Header().Set("content-type", "application-json")
962		fmt.Fprintf(w, updateWorkerRouteEntResponse)
963	})
964
965	route := WorkerRoute{Pattern: "app1.example.com/*"}
966	_, err := client.UpdateWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1", route)
967	assert.NoError(t, err)
968}
969