1package testing
2
3import (
4	"net/url"
5	"reflect"
6	"testing"
7	"time"
8
9	"github.com/gophercloud/gophercloud"
10	th "github.com/gophercloud/gophercloud/testhelper"
11)
12
13func TestMaybeString(t *testing.T) {
14	testString := ""
15	var expected *string
16	actual := gophercloud.MaybeString(testString)
17	th.CheckDeepEquals(t, expected, actual)
18
19	testString = "carol"
20	expected = &testString
21	actual = gophercloud.MaybeString(testString)
22	th.CheckDeepEquals(t, expected, actual)
23}
24
25func TestMaybeInt(t *testing.T) {
26	testInt := 0
27	var expected *int
28	actual := gophercloud.MaybeInt(testInt)
29	th.CheckDeepEquals(t, expected, actual)
30
31	testInt = 4
32	expected = &testInt
33	actual = gophercloud.MaybeInt(testInt)
34	th.CheckDeepEquals(t, expected, actual)
35}
36
37func TestBuildQueryString(t *testing.T) {
38	type testVar string
39	iFalse := false
40	opts := struct {
41		J  int               `q:"j"`
42		R  string            `q:"r" required:"true"`
43		C  bool              `q:"c"`
44		S  []string          `q:"s"`
45		TS []testVar         `q:"ts"`
46		TI []int             `q:"ti"`
47		F  *bool             `q:"f"`
48		M  map[string]string `q:"m"`
49	}{
50		J:  2,
51		R:  "red",
52		C:  true,
53		S:  []string{"one", "two", "three"},
54		TS: []testVar{"a", "b"},
55		TI: []int{1, 2},
56		F:  &iFalse,
57		M:  map[string]string{"k1": "success1"},
58	}
59	expected := &url.URL{RawQuery: "c=true&f=false&j=2&m=%7B%27k1%27%3A%27success1%27%7D&r=red&s=one&s=two&s=three&ti=1&ti=2&ts=a&ts=b"}
60	actual, err := gophercloud.BuildQueryString(&opts)
61	if err != nil {
62		t.Errorf("Error building query string: %v", err)
63	}
64	th.CheckDeepEquals(t, expected, actual)
65
66	opts = struct {
67		J  int               `q:"j"`
68		R  string            `q:"r" required:"true"`
69		C  bool              `q:"c"`
70		S  []string          `q:"s"`
71		TS []testVar         `q:"ts"`
72		TI []int             `q:"ti"`
73		F  *bool             `q:"f"`
74		M  map[string]string `q:"m"`
75	}{
76		J: 2,
77		C: true,
78	}
79	_, err = gophercloud.BuildQueryString(&opts)
80	if err == nil {
81		t.Errorf("Expected error: 'Required field not set'")
82	}
83	th.CheckDeepEquals(t, expected, actual)
84
85	_, err = gophercloud.BuildQueryString(map[string]interface{}{"Number": 4})
86	if err == nil {
87		t.Errorf("Expected error: 'Options type is not a struct'")
88	}
89}
90
91func TestBuildHeaders(t *testing.T) {
92	testStruct := struct {
93		Accept string `h:"Accept"`
94		Num    int    `h:"Number" required:"true"`
95		Style  bool   `h:"Style"`
96	}{
97		Accept: "application/json",
98		Num:    4,
99		Style:  true,
100	}
101	expected := map[string]string{"Accept": "application/json", "Number": "4", "Style": "true"}
102	actual, err := gophercloud.BuildHeaders(&testStruct)
103	th.CheckNoErr(t, err)
104	th.CheckDeepEquals(t, expected, actual)
105
106	testStruct.Num = 0
107	_, err = gophercloud.BuildHeaders(&testStruct)
108	if err == nil {
109		t.Errorf("Expected error: 'Required header not set'")
110	}
111
112	_, err = gophercloud.BuildHeaders(map[string]interface{}{"Number": 4})
113	if err == nil {
114		t.Errorf("Expected error: 'Options type is not a struct'")
115	}
116}
117
118func TestQueriesAreEscaped(t *testing.T) {
119	type foo struct {
120		Name  string `q:"something"`
121		Shape string `q:"else"`
122	}
123
124	expected := &url.URL{RawQuery: "else=Triangl+e&something=blah%2B%3F%21%21foo"}
125
126	actual, err := gophercloud.BuildQueryString(foo{Name: "blah+?!!foo", Shape: "Triangl e"})
127	th.AssertNoErr(t, err)
128
129	th.AssertDeepEquals(t, expected, actual)
130}
131
132func TestBuildRequestBody(t *testing.T) {
133	type PasswordCredentials struct {
134		Username string `json:"username" required:"true"`
135		Password string `json:"password" required:"true"`
136	}
137
138	type TokenCredentials struct {
139		ID string `json:"id,omitempty" required:"true"`
140	}
141
142	type orFields struct {
143		Filler int `json:"filler,omitempty"`
144		F1     int `json:"f1,omitempty" or:"F2"`
145		F2     int `json:"f2,omitempty" or:"F1"`
146	}
147
148	// AuthOptions wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
149	// interface.
150	type AuthOptions struct {
151		PasswordCredentials *PasswordCredentials `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
152
153		// The TenantID and TenantName fields are optional for the Identity V2 API.
154		// Some providers allow you to specify a TenantName instead of the TenantId.
155		// Some require both. Your provider's authentication policies will determine
156		// how these fields influence authentication.
157		TenantID   string `json:"tenantId,omitempty"`
158		TenantName string `json:"tenantName,omitempty"`
159
160		// TokenCredentials allows users to authenticate (possibly as another user) with an
161		// authentication token ID.
162		TokenCredentials *TokenCredentials `json:"token,omitempty" xor:"PasswordCredentials"`
163
164		OrFields *orFields `json:"or_fields,omitempty"`
165	}
166
167	var successCases = []struct {
168		opts     AuthOptions
169		expected map[string]interface{}
170	}{
171		{
172			AuthOptions{
173				PasswordCredentials: &PasswordCredentials{
174					Username: "me",
175					Password: "swordfish",
176				},
177			},
178			map[string]interface{}{
179				"auth": map[string]interface{}{
180					"passwordCredentials": map[string]interface{}{
181						"password": "swordfish",
182						"username": "me",
183					},
184				},
185			},
186		},
187		{
188			AuthOptions{
189				TokenCredentials: &TokenCredentials{
190					ID: "1234567",
191				},
192			},
193			map[string]interface{}{
194				"auth": map[string]interface{}{
195					"token": map[string]interface{}{
196						"id": "1234567",
197					},
198				},
199			},
200		},
201	}
202
203	for _, successCase := range successCases {
204		actual, err := gophercloud.BuildRequestBody(successCase.opts, "auth")
205		th.AssertNoErr(t, err)
206		th.AssertDeepEquals(t, successCase.expected, actual)
207	}
208
209	var failCases = []struct {
210		opts     AuthOptions
211		expected error
212	}{
213		{
214			AuthOptions{
215				TenantID:   "987654321",
216				TenantName: "me",
217			},
218			gophercloud.ErrMissingInput{},
219		},
220		{
221			AuthOptions{
222				TokenCredentials: &TokenCredentials{
223					ID: "1234567",
224				},
225				PasswordCredentials: &PasswordCredentials{
226					Username: "me",
227					Password: "swordfish",
228				},
229			},
230			gophercloud.ErrMissingInput{},
231		},
232		{
233			AuthOptions{
234				PasswordCredentials: &PasswordCredentials{
235					Password: "swordfish",
236				},
237			},
238			gophercloud.ErrMissingInput{},
239		},
240		{
241			AuthOptions{
242				PasswordCredentials: &PasswordCredentials{
243					Username: "me",
244					Password: "swordfish",
245				},
246				OrFields: &orFields{
247					Filler: 2,
248				},
249			},
250			gophercloud.ErrMissingInput{},
251		},
252	}
253
254	for _, failCase := range failCases {
255		_, err := gophercloud.BuildRequestBody(failCase.opts, "auth")
256		th.AssertDeepEquals(t, reflect.TypeOf(failCase.expected), reflect.TypeOf(err))
257	}
258
259	createdAt := time.Date(2018, 1, 4, 10, 00, 12, 0, time.UTC)
260	var complexFields = struct {
261		Username  string     `json:"username" required:"true"`
262		CreatedAt *time.Time `json:"-"`
263	}{
264		Username:  "jdoe",
265		CreatedAt: &createdAt,
266	}
267
268	expectedComplexFields := map[string]interface{}{
269		"username": "jdoe",
270	}
271
272	actual, err := gophercloud.BuildRequestBody(complexFields, "")
273	th.AssertNoErr(t, err)
274	th.AssertDeepEquals(t, expectedComplexFields, actual)
275
276}
277