1package godo
2
3import (
4	"encoding/json"
5	"fmt"
6	"io/ioutil"
7	"net/http"
8	"reflect"
9	"strings"
10	"testing"
11)
12
13func TestProjects_List(t *testing.T) {
14	setup()
15	defer teardown()
16
17	expectedProjects := []Project{
18		{
19			ID:   "project-1",
20			Name: "project-1",
21		},
22		{
23			ID:   "project-2",
24			Name: "project-2",
25		},
26	}
27
28	mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) {
29		testMethod(t, r, http.MethodGet)
30		resp, _ := json.Marshal(expectedProjects)
31		fmt.Fprint(w, fmt.Sprintf(`{"projects":%s, "meta": {"total": 2}}`, string(resp)))
32	})
33
34	projects, resp, err := client.Projects.List(ctx, nil)
35	if err != nil {
36		t.Errorf("Projects.List returned error: %v", err)
37	}
38
39	if !reflect.DeepEqual(projects, expectedProjects) {
40		t.Errorf("Projects.List returned projects %+v, expected %+v", projects, expectedProjects)
41	}
42
43	expectedMeta := &Meta{Total: 2}
44	if !reflect.DeepEqual(resp.Meta, expectedMeta) {
45		t.Errorf("Projects.List returned meta %+v, expected %+v", resp.Meta, expectedMeta)
46	}
47}
48
49func TestProjects_ListWithMultiplePages(t *testing.T) {
50	setup()
51	defer teardown()
52
53	mockResp := `
54	{
55		"projects": [
56			{
57				"uuid": "project-1",
58				"name": "project-1"
59			},
60			{
61				"uuid": "project-2",
62				"name": "project-2"
63			}
64		],
65		"links": {
66			"pages": {
67				"next": "http://example.com/v2/projects?page=2"
68			}
69		}
70	}`
71
72	mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) {
73		testMethod(t, r, http.MethodGet)
74		fmt.Fprint(w, mockResp)
75	})
76
77	_, resp, err := client.Projects.List(ctx, nil)
78	if err != nil {
79		t.Errorf("Projects.List returned error: %v", err)
80	}
81
82	checkCurrentPage(t, resp, 1)
83}
84
85func TestProjects_ListWithPageNumber(t *testing.T) {
86	setup()
87	defer teardown()
88
89	mockResp := `
90	{
91		"projects": [
92			{
93				"uuid": "project-1",
94				"name": "project-1"
95			},
96			{
97				"uuid": "project-2",
98				"name": "project-2"
99			}
100		],
101		"links": {
102			"pages": {
103				"next": "http://example.com/v2/projects?page=3",
104				"prev": "http://example.com/v2/projects?page=1",
105				"last": "http://example.com/v2/projects?page=3",
106				"first": "http://example.com/v2/projects?page=1"
107			}
108		}
109	}`
110
111	mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) {
112		testMethod(t, r, http.MethodGet)
113		fmt.Fprint(w, mockResp)
114	})
115
116	_, resp, err := client.Projects.List(ctx, &ListOptions{Page: 2})
117	if err != nil {
118		t.Errorf("Projects.List returned error: %v", err)
119	}
120
121	checkCurrentPage(t, resp, 2)
122}
123
124func TestProjects_GetDefault(t *testing.T) {
125	setup()
126	defer teardown()
127
128	project := &Project{
129		ID:   "project-1",
130		Name: "project-1",
131	}
132
133	mux.HandleFunc("/v2/projects/default", func(w http.ResponseWriter, r *http.Request) {
134		testMethod(t, r, http.MethodGet)
135		resp, _ := json.Marshal(project)
136		fmt.Fprint(w, fmt.Sprintf(`{"project":%s}`, string(resp)))
137	})
138
139	resp, _, err := client.Projects.GetDefault(ctx)
140	if err != nil {
141		t.Errorf("Projects.GetDefault returned error: %v", err)
142	}
143
144	if !reflect.DeepEqual(resp, project) {
145		t.Errorf("Projects.GetDefault returned %+v, expected %+v", resp, project)
146	}
147}
148
149func TestProjects_GetWithUUID(t *testing.T) {
150	setup()
151	defer teardown()
152
153	project := &Project{
154		ID:   "project-1",
155		Name: "project-1",
156	}
157
158	mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) {
159		testMethod(t, r, http.MethodGet)
160		resp, _ := json.Marshal(project)
161		fmt.Fprint(w, fmt.Sprintf(`{"project":%s}`, string(resp)))
162	})
163
164	resp, _, err := client.Projects.Get(ctx, "project-1")
165	if err != nil {
166		t.Errorf("Projects.Get returned error: %v", err)
167	}
168
169	if !reflect.DeepEqual(resp, project) {
170		t.Errorf("Projects.Get returned %+v, expected %+v", resp, project)
171	}
172}
173
174func TestProjects_Create(t *testing.T) {
175	setup()
176	defer teardown()
177
178	createRequest := &CreateProjectRequest{
179		Name:        "my project",
180		Description: "for my stuff",
181		Purpose:     "Just trying out DigitalOcean",
182		Environment: "Production",
183	}
184
185	createResp := &Project{
186		ID:          "project-id",
187		Name:        createRequest.Name,
188		Description: createRequest.Description,
189		Purpose:     createRequest.Purpose,
190		Environment: createRequest.Environment,
191	}
192
193	mux.HandleFunc("/v2/projects", func(w http.ResponseWriter, r *http.Request) {
194		v := new(CreateProjectRequest)
195		err := json.NewDecoder(r.Body).Decode(v)
196		if err != nil {
197			t.Fatalf("decode json: %v", err)
198		}
199
200		testMethod(t, r, http.MethodPost)
201		if !reflect.DeepEqual(v, createRequest) {
202			t.Errorf("Request body = %+v, expected %+v", v, createRequest)
203		}
204
205		resp, _ := json.Marshal(createResp)
206		fmt.Fprintf(w, fmt.Sprintf(`{"project":%s}`, string(resp)))
207	})
208
209	project, _, err := client.Projects.Create(ctx, createRequest)
210	if err != nil {
211		t.Errorf("Projects.Create returned error: %v", err)
212	}
213
214	if !reflect.DeepEqual(project, createResp) {
215		t.Errorf("Projects.Create returned %+v, expected %+v", project, createResp)
216	}
217}
218
219func TestProjects_UpdateWithOneAttribute(t *testing.T) {
220	setup()
221	defer teardown()
222
223	updateRequest := &UpdateProjectRequest{
224		Name: "my-great-project",
225	}
226	updateResp := &Project{
227		ID:          "project-id",
228		Name:        updateRequest.Name.(string),
229		Description: "some-other-description",
230		Purpose:     "some-other-purpose",
231		Environment: "some-other-env",
232		IsDefault:   false,
233	}
234
235	mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) {
236		reqBytes, respErr := ioutil.ReadAll(r.Body)
237		if respErr != nil {
238			t.Error("projects mock didn't work")
239		}
240
241		req := strings.TrimSuffix(string(reqBytes), "\n")
242		expectedReq := `{"name":"my-great-project","description":null,"purpose":null,"environment":null,"is_default":null}`
243		if req != expectedReq {
244			t.Errorf("projects req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req)
245		}
246
247		resp, _ := json.Marshal(updateResp)
248		fmt.Fprintf(w, fmt.Sprintf(`{"project":%s}`, string(resp)))
249	})
250
251	project, _, err := client.Projects.Update(ctx, "project-1", updateRequest)
252	if err != nil {
253		t.Errorf("Projects.Update returned error: %v", err)
254	}
255	if !reflect.DeepEqual(project, updateResp) {
256		t.Errorf("Projects.Update returned %+v, expected %+v", project, updateResp)
257	}
258}
259
260func TestProjects_UpdateWithAllAttributes(t *testing.T) {
261	setup()
262	defer teardown()
263
264	updateRequest := &UpdateProjectRequest{
265		Name:        "my-great-project",
266		Description: "some-description",
267		Purpose:     "some-purpose",
268		Environment: "some-env",
269		IsDefault:   true,
270	}
271	updateResp := &Project{
272		ID:          "project-id",
273		Name:        updateRequest.Name.(string),
274		Description: updateRequest.Description.(string),
275		Purpose:     updateRequest.Purpose.(string),
276		Environment: updateRequest.Environment.(string),
277		IsDefault:   updateRequest.IsDefault.(bool),
278	}
279
280	mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) {
281		reqBytes, respErr := ioutil.ReadAll(r.Body)
282		if respErr != nil {
283			t.Error("projects mock didn't work")
284		}
285
286		req := strings.TrimSuffix(string(reqBytes), "\n")
287		expectedReq := `{"name":"my-great-project","description":"some-description","purpose":"some-purpose","environment":"some-env","is_default":true}`
288		if req != expectedReq {
289			t.Errorf("projects req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req)
290		}
291
292		resp, _ := json.Marshal(updateResp)
293		fmt.Fprintf(w, fmt.Sprintf(`{"project":%s}`, string(resp)))
294	})
295
296	project, _, err := client.Projects.Update(ctx, "project-1", updateRequest)
297	if err != nil {
298		t.Errorf("Projects.Update returned error: %v", err)
299	}
300	if !reflect.DeepEqual(project, updateResp) {
301		t.Errorf("Projects.Update returned %+v, expected %+v", project, updateResp)
302	}
303}
304
305func TestProjects_Destroy(t *testing.T) {
306	setup()
307	defer teardown()
308
309	mux.HandleFunc("/v2/projects/project-1", func(w http.ResponseWriter, r *http.Request) {
310		testMethod(t, r, http.MethodDelete)
311	})
312
313	_, err := client.Projects.Delete(ctx, "project-1")
314	if err != nil {
315		t.Errorf("Projects.Delete returned error: %v", err)
316	}
317}
318
319func TestProjects_ListResources(t *testing.T) {
320	setup()
321	defer teardown()
322
323	expectedResources := []ProjectResource{
324		{
325			URN:        "do:droplet:1",
326			AssignedAt: "2018-09-27 00:00:00",
327			Links: &ProjectResourceLinks{
328				Self: "http://example.com/v2/droplets/1",
329			},
330		},
331		{
332			URN:        "do:floatingip:1.2.3.4",
333			AssignedAt: "2018-09-27 00:00:00",
334			Links: &ProjectResourceLinks{
335				Self: "http://example.com/v2/floating_ips/1.2.3.4",
336			},
337		},
338	}
339
340	mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
341		testMethod(t, r, http.MethodGet)
342		resp, _ := json.Marshal(expectedResources)
343		fmt.Fprint(w, fmt.Sprintf(`{"resources":%s, "meta": {"total": 2}}`, string(resp)))
344	})
345
346	resources, resp, err := client.Projects.ListResources(ctx, "project-1", nil)
347	if err != nil {
348		t.Errorf("Projects.List returned error: %v", err)
349	}
350
351	if !reflect.DeepEqual(resources, expectedResources) {
352		t.Errorf("Projects.ListResources returned resources %+v, expected %+v", resources, expectedResources)
353	}
354
355	expectedMeta := &Meta{Total: 2}
356	if !reflect.DeepEqual(resp.Meta, expectedMeta) {
357		t.Errorf("Projects.ListResources returned meta %+v, expected %+v", resp.Meta, expectedMeta)
358	}
359}
360
361func TestProjects_ListResourcesWithMultiplePages(t *testing.T) {
362	setup()
363	defer teardown()
364
365	mockResp := `
366	{
367		"resources": [
368			{
369				"urn": "do:droplet:1",
370				"assigned_at": "2018-09-27 00:00:00",
371				"links": {
372					"self": "http://example.com/v2/droplets/1"
373				}
374			},
375			{
376				"urn": "do:floatingip:1.2.3.4",
377				"assigned_at": "2018-09-27 00:00:00",
378				"links": {
379					"self": "http://example.com/v2/floating_ips/1.2.3.4"
380				}
381			}
382		],
383		"links": {
384			"pages": {
385				"next": "http://example.com/v2/projects/project-1/resources?page=2"
386			}
387		}
388	}`
389
390	mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
391		testMethod(t, r, http.MethodGet)
392		fmt.Fprint(w, mockResp)
393	})
394
395	_, resp, err := client.Projects.ListResources(ctx, "project-1", nil)
396	if err != nil {
397		t.Errorf("Projects.ListResources returned error: %v", err)
398	}
399
400	checkCurrentPage(t, resp, 1)
401}
402
403func TestProjects_ListResourcesWithPageNumber(t *testing.T) {
404	setup()
405	defer teardown()
406
407	mockResp := `
408	{
409		"resources": [
410			{
411				"urn": "do:droplet:1",
412				"assigned_at": "2018-09-27 00:00:00",
413				"links": {
414					"self": "http://example.com/v2/droplets/1"
415				}
416			},
417			{
418				"urn": "do:floatingip:1.2.3.4",
419				"assigned_at": "2018-09-27 00:00:00",
420				"links": {
421					"self": "http://example.com/v2/floating_ips/1.2.3.4"
422				}
423			}
424		],
425		"links": {
426			"pages": {
427				"next": "http://example.com/v2/projects/project-1/resources?page=3",
428				"prev": "http://example.com/v2/projects/project-1/resources?page=1",
429				"last": "http://example.com/v2/projects/project-1/resources?page=3",
430				"first": "http://example.com/v2/projects/project-1/resources?page=1"
431			}
432		}
433	}`
434
435	mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
436		testMethod(t, r, http.MethodGet)
437		fmt.Fprint(w, mockResp)
438	})
439
440	_, resp, err := client.Projects.ListResources(ctx, "project-1", &ListOptions{Page: 2})
441	if err != nil {
442		t.Errorf("Projects.ListResources returned error: %v", err)
443	}
444
445	checkCurrentPage(t, resp, 2)
446}
447
448func TestProjects_AssignFleetResourcesWithTypes(t *testing.T) {
449	setup()
450	defer teardown()
451
452	assignableResources := []interface{}{
453		&Droplet{ID: 1234},
454		&FloatingIP{IP: "1.2.3.4"},
455	}
456
457	mockResp := `
458	{
459		"resources": [
460			{
461				"urn": "do:droplet:1234",
462				"assigned_at": "2018-09-27 00:00:00",
463				"links": {
464					"self": "http://example.com/v2/droplets/1"
465				}
466			},
467			{
468				"urn": "do:floatingip:1.2.3.4",
469				"assigned_at": "2018-09-27 00:00:00",
470				"links": {
471					"self": "http://example.com/v2/floating_ips/1.2.3.4"
472				}
473			}
474		]
475	}`
476
477	mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
478		testMethod(t, r, http.MethodPost)
479		reqBytes, respErr := ioutil.ReadAll(r.Body)
480		if respErr != nil {
481			t.Error("projects mock didn't work")
482		}
483
484		req := strings.TrimSuffix(string(reqBytes), "\n")
485		expectedReq := `{"resources":["do:droplet:1234","do:floatingip:1.2.3.4"]}`
486		if req != expectedReq {
487			t.Errorf("projects assign req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req)
488		}
489
490		fmt.Fprint(w, mockResp)
491	})
492
493	_, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...)
494	if err != nil {
495		t.Errorf("Projects.AssignResources returned error: %v", err)
496	}
497}
498
499func TestProjects_AssignFleetResourcesWithStrings(t *testing.T) {
500	setup()
501	defer teardown()
502
503	assignableResources := []interface{}{
504		"do:droplet:1234",
505		"do:floatingip:1.2.3.4",
506	}
507
508	mockResp := `
509	{
510		"resources": [
511			{
512				"urn": "do:droplet:1234",
513				"assigned_at": "2018-09-27 00:00:00",
514				"links": {
515					"self": "http://example.com/v2/droplets/1"
516				}
517			},
518			{
519				"urn": "do:floatingip:1.2.3.4",
520				"assigned_at": "2018-09-27 00:00:00",
521				"links": {
522					"self": "http://example.com/v2/floating_ips/1.2.3.4"
523				}
524			}
525		]
526	}`
527
528	mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
529		testMethod(t, r, http.MethodPost)
530		reqBytes, respErr := ioutil.ReadAll(r.Body)
531		if respErr != nil {
532			t.Error("projects mock didn't work")
533		}
534
535		req := strings.TrimSuffix(string(reqBytes), "\n")
536		expectedReq := `{"resources":["do:droplet:1234","do:floatingip:1.2.3.4"]}`
537		if req != expectedReq {
538			t.Errorf("projects assign req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req)
539		}
540
541		fmt.Fprint(w, mockResp)
542	})
543
544	_, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...)
545	if err != nil {
546		t.Errorf("Projects.AssignResources returned error: %v", err)
547	}
548}
549
550func TestProjects_AssignFleetResourcesWithStringsAndTypes(t *testing.T) {
551	setup()
552	defer teardown()
553
554	assignableResources := []interface{}{
555		"do:droplet:1234",
556		&FloatingIP{IP: "1.2.3.4"},
557	}
558
559	mockResp := `
560	{
561		"resources": [
562			{
563				"urn": "do:droplet:1234",
564				"assigned_at": "2018-09-27 00:00:00",
565				"links": {
566					"self": "http://example.com/v2/droplets/1"
567				}
568			},
569			{
570				"urn": "do:floatingip:1.2.3.4",
571				"assigned_at": "2018-09-27 00:00:00",
572				"links": {
573					"self": "http://example.com/v2/floating_ips/1.2.3.4"
574				}
575			}
576		]
577	}`
578
579	mux.HandleFunc("/v2/projects/project-1/resources", func(w http.ResponseWriter, r *http.Request) {
580		testMethod(t, r, http.MethodPost)
581		reqBytes, respErr := ioutil.ReadAll(r.Body)
582		if respErr != nil {
583			t.Error("projects mock didn't work")
584		}
585
586		req := strings.TrimSuffix(string(reqBytes), "\n")
587		expectedReq := `{"resources":["do:droplet:1234","do:floatingip:1.2.3.4"]}`
588		if req != expectedReq {
589			t.Errorf("projects assign req didn't match up:\n expected %+v\n got %+v\n", expectedReq, req)
590		}
591
592		fmt.Fprint(w, mockResp)
593	})
594
595	_, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...)
596	if err != nil {
597		t.Errorf("Projects.AssignResources returned error: %v", err)
598	}
599}
600
601func TestProjects_AssignFleetResourcesWithTypeWithoutURNReturnsError(t *testing.T) {
602	setup()
603	defer teardown()
604
605	type fakeType struct{}
606
607	assignableResources := []interface{}{
608		fakeType{},
609	}
610
611	_, _, err := client.Projects.AssignResources(ctx, "project-1", assignableResources...)
612	if err == nil {
613		t.Errorf("expected Projects.AssignResources to error, but it did not")
614	}
615
616	if err.Error() != "godo.fakeType must either be a string or have a valid URN method" {
617		t.Errorf("Projects.AssignResources returned the wrong error: %v", err)
618	}
619}
620