1package hcloud
2
3import (
4	"encoding/json"
5	"testing"
6	"time"
7
8	"github.com/google/go-cmp/cmp"
9
10	"github.com/hetznercloud/hcloud-go/hcloud/schema"
11)
12
13func TestActionFromSchema(t *testing.T) {
14	data := []byte(`{
15		"id": 1,
16		"command": "create_server",
17		"status": "success",
18		"progress": 100,
19		"started": "2016-01-30T23:55:00Z",
20		"finished": "2016-01-30T23:56:13Z",
21		"resources": [
22			{
23				"id": 42,
24				"type": "server"
25			}
26		],
27		"error": {
28			"code": "action_failed",
29			"message": "Action failed"
30		}
31	}`)
32
33	var s schema.Action
34	if err := json.Unmarshal(data, &s); err != nil {
35		t.Fatal(err)
36	}
37	action := ActionFromSchema(s)
38
39	if action.ID != 1 {
40		t.Errorf("unexpected ID: %v", action.ID)
41	}
42	if action.Command != "create_server" {
43		t.Errorf("unexpected command: %v", action.Command)
44	}
45	if action.Status != "success" {
46		t.Errorf("unexpected status: %v", action.Status)
47	}
48	if action.Progress != 100 {
49		t.Errorf("unexpected progress: %d", action.Progress)
50	}
51	if !action.Started.Equal(time.Date(2016, 1, 30, 23, 55, 0, 0, time.UTC)) {
52		t.Errorf("unexpected started: %v", action.Started)
53	}
54	if !action.Finished.Equal(time.Date(2016, 1, 30, 23, 56, 13, 0, time.UTC)) {
55		t.Errorf("unexpected finished: %v", action.Started)
56	}
57	if action.ErrorCode != "action_failed" {
58		t.Errorf("unexpected error code: %v", action.ErrorCode)
59	}
60	if action.ErrorMessage != "Action failed" {
61		t.Errorf("unexpected error message: %v", action.ErrorMessage)
62	}
63	if len(action.Resources) == 1 {
64		if action.Resources[0].ID != 42 {
65			t.Errorf("unexpected id in resources[0].ID: %v", action.Resources[0].ID)
66		}
67		if action.Resources[0].Type != ActionResourceTypeServer {
68			t.Errorf("unexpected type in resources[0].Type: %v", action.Resources[0].Type)
69		}
70	} else {
71		t.Errorf("unexpected number of resources")
72	}
73}
74
75func TestActionsFromSchema(t *testing.T) {
76	data := []byte(`[
77		{
78			"id": 13,
79			"command": "create_server"
80		},
81		{
82			"id": 14,
83			"command": "start_server"
84		}
85	]`)
86	var s []schema.Action
87	if err := json.Unmarshal(data, &s); err != nil {
88		t.Fatal(err)
89	}
90	actions := ActionsFromSchema(s)
91	if len(actions) != 2 || actions[0].ID != 13 || actions[1].ID != 14 {
92		t.Fatal("unexpected actions")
93	}
94}
95
96func TestFloatingIPFromSchema(t *testing.T) {
97	t.Run("IPv6", func(t *testing.T) {
98		data := []byte(`{
99			"id": 4711,
100			"name": "Web Frontend",
101			"description": "Web Frontend",
102			"created":"2017-08-16T17:29:14+00:00",
103			"ip": "2001:db8::/64",
104			"type": "ipv6",
105			"server": null,
106			"dns_ptr": [],
107			"blocked": true,
108			"home_location": {
109				"id": 1,
110				"name": "fsn1",
111				"description": "Falkenstein DC Park 1",
112				"country": "DE",
113				"city": "Falkenstein",
114				"latitude": 50.47612,
115				"longitude": 12.370071,
116				"network_zone": "eu-central"
117			},
118			"protection": {
119				"delete": true
120			},
121			"labels": {
122				"key": "value",
123				"key2": "value2"
124			}
125		}`)
126
127		var s schema.FloatingIP
128		if err := json.Unmarshal(data, &s); err != nil {
129			t.Fatal(err)
130		}
131		floatingIP := FloatingIPFromSchema(s)
132
133		if floatingIP.ID != 4711 {
134			t.Errorf("unexpected ID: %v", floatingIP.ID)
135		}
136		if !floatingIP.Blocked {
137			t.Errorf("unexpected value for Blocked: %v", floatingIP.Blocked)
138		}
139		if floatingIP.Name != "Web Frontend" {
140			t.Errorf("unexpected name: %v", floatingIP.Name)
141		}
142		if floatingIP.Description != "Web Frontend" {
143			t.Errorf("unexpected description: %v", floatingIP.Description)
144		}
145		if floatingIP.IP.String() != "2001:db8::" {
146			t.Errorf("unexpected IP: %v", floatingIP.IP)
147		}
148		if floatingIP.Type != FloatingIPTypeIPv6 {
149			t.Errorf("unexpected Type: %v", floatingIP.Type)
150		}
151		if floatingIP.Server != nil {
152			t.Errorf("unexpected Server: %v", floatingIP.Server)
153		}
154		if floatingIP.DNSPtr == nil || floatingIP.DNSPtrForIP(floatingIP.IP) != "" {
155			t.Errorf("unexpected DNS ptr: %v", floatingIP.DNSPtr)
156		}
157		if floatingIP.HomeLocation == nil || floatingIP.HomeLocation.ID != 1 {
158			t.Errorf("unexpected home location: %v", floatingIP.HomeLocation)
159		}
160		if !floatingIP.Protection.Delete {
161			t.Errorf("unexpected Protection.Delete: %v", floatingIP.Protection.Delete)
162		}
163		if floatingIP.Labels["key"] != "value" || floatingIP.Labels["key2"] != "value2" {
164			t.Errorf("unexpected Labels: %v", floatingIP.Labels)
165		}
166		if !floatingIP.Created.Equal(time.Date(2017, 8, 16, 17, 29, 14, 0, time.UTC)) {
167			t.Errorf("unexpected created date: %v", floatingIP.Created)
168		}
169	})
170
171	t.Run("IPv4", func(t *testing.T) {
172		data := []byte(`{
173			"id": 4711,
174			"description": "Web Frontend",
175			"ip": "131.232.99.1",
176			"type": "ipv4",
177			"server": 42,
178			"dns_ptr": [{
179				"ip": "131.232.99.1",
180				"dns_ptr": "fip01.example.com"
181			}],
182			"blocked": false,
183			"home_location": {
184				"id": 1,
185				"name": "fsn1",
186				"description": "Falkenstein DC Park 1",
187				"country": "DE",
188				"city": "Falkenstein",
189				"latitude": 50.47612,
190				"longitude": 12.370071
191			}
192		}`)
193
194		var s schema.FloatingIP
195		if err := json.Unmarshal(data, &s); err != nil {
196			t.Fatal(err)
197		}
198		floatingIP := FloatingIPFromSchema(s)
199
200		if floatingIP.ID != 4711 {
201			t.Errorf("unexpected ID: %v", floatingIP.ID)
202		}
203		if floatingIP.Blocked {
204			t.Errorf("unexpected value for Blocked: %v", floatingIP.Blocked)
205		}
206		if floatingIP.Description != "Web Frontend" {
207			t.Errorf("unexpected description: %v", floatingIP.Description)
208		}
209		if floatingIP.IP.String() != "131.232.99.1" {
210			t.Errorf("unexpected IP: %v", floatingIP.IP)
211		}
212		if floatingIP.Type != FloatingIPTypeIPv4 {
213			t.Errorf("unexpected type: %v", floatingIP.Type)
214		}
215		if floatingIP.Server == nil || floatingIP.Server.ID != 42 {
216			t.Errorf("unexpected server: %v", floatingIP.Server)
217		}
218		if floatingIP.DNSPtr == nil || floatingIP.DNSPtrForIP(floatingIP.IP) != "fip01.example.com" {
219			t.Errorf("unexpected DNS ptr: %v", floatingIP.DNSPtr)
220		}
221		if floatingIP.HomeLocation == nil || floatingIP.HomeLocation.ID != 1 {
222			t.Errorf("unexpected home location: %v", floatingIP.HomeLocation)
223		}
224	})
225}
226
227func TestISOFromSchema(t *testing.T) {
228	data := []byte(`{
229		"id": 4711,
230		"name": "FreeBSD-11.0-RELEASE-amd64-dvd1",
231		"description": "FreeBSD 11.0 x64",
232		"type": "public",
233		"deprecated": "2018-02-28T00:00:00+00:00"
234	}`)
235
236	var s schema.ISO
237	if err := json.Unmarshal(data, &s); err != nil {
238		t.Fatal(err)
239	}
240	iso := ISOFromSchema(s)
241	if iso.ID != 4711 {
242		t.Errorf("unexpected ID: %v", iso.ID)
243	}
244	if iso.Name != "FreeBSD-11.0-RELEASE-amd64-dvd1" {
245		t.Errorf("unexpected name: %v", iso.Name)
246	}
247	if iso.Description != "FreeBSD 11.0 x64" {
248		t.Errorf("unexpected description: %v", iso.Description)
249	}
250	if iso.Type != ISOTypePublic {
251		t.Errorf("unexpected type: %v", iso.Type)
252	}
253	if iso.Deprecated.IsZero() {
254		t.Errorf("unexpected value for deprecated: %v", iso.Deprecated)
255	}
256}
257
258func TestDatacenterFromSchema(t *testing.T) {
259	data := []byte(`{
260		"id": 1,
261		"name": "fsn1-dc8",
262		"description": "Falkenstein 1 DC 8",
263		"location": {
264			"id": 1,
265			"name": "fsn1",
266			"description": "Falkenstein DC Park 1",
267			"country": "DE",
268			"city": "Falkenstein",
269			"latitude": 50.47612,
270			"longitude": 12.370071,
271			"network_zone": "eu-central"
272		},
273		"server_types": {
274			"supported": [
275				1,
276				1,
277				2,
278				3
279			],
280			"available": [
281				1,
282				1,
283				2,
284				3
285			]
286		}
287	}`)
288
289	var s schema.Datacenter
290	if err := json.Unmarshal(data, &s); err != nil {
291		t.Fatal(err)
292	}
293	datacenter := DatacenterFromSchema(s)
294	if datacenter.ID != 1 {
295		t.Errorf("unexpected ID: %v", datacenter.ID)
296	}
297	if datacenter.Name != "fsn1-dc8" {
298		t.Errorf("unexpected Name: %v", datacenter.Name)
299	}
300	if datacenter.Location == nil || datacenter.Location.ID != 1 {
301		t.Errorf("unexpected Location: %v", datacenter.Location)
302	}
303	if len(datacenter.ServerTypes.Available) != 4 {
304		t.Errorf("unexpected ServerTypes.Available (should be 4): %v", len(datacenter.ServerTypes.Available))
305	}
306	if len(datacenter.ServerTypes.Supported) != 4 {
307		t.Errorf("unexpected ServerTypes.Supported length (should be 4): %v", len(datacenter.ServerTypes.Supported))
308	}
309}
310
311func TestLocationFromSchema(t *testing.T) {
312	data := []byte(`{
313		"id": 1,
314		"name": "fsn1",
315		"description": "Falkenstein DC Park 1",
316		"country": "DE",
317		"city": "Falkenstein",
318		"latitude": 50.47612,
319		"longitude": 12.370071,
320		"network_zone": "eu-central"
321	}`)
322
323	var s schema.Location
324	if err := json.Unmarshal(data, &s); err != nil {
325		t.Fatal(err)
326	}
327	location := LocationFromSchema(s)
328	if location.ID != 1 {
329		t.Errorf("unexpected ID: %v", location.ID)
330	}
331	if location.Name != "fsn1" {
332		t.Errorf("unexpected Name: %v", location.Name)
333	}
334	if location.Description != "Falkenstein DC Park 1" {
335		t.Errorf("unexpected Description: %v", location.Description)
336	}
337	if location.Country != "DE" {
338		t.Errorf("unexpected Country: %v", location.Country)
339	}
340	if location.City != "Falkenstein" {
341		t.Errorf("unexpected City: %v", location.City)
342	}
343	if location.Latitude != 50.47612 {
344		t.Errorf("unexpected Latitude: %v", location.Latitude)
345	}
346	if location.Longitude != 12.370071 {
347		t.Errorf("unexpected Longitude: %v", location.Longitude)
348	}
349	if location.NetworkZone != "eu-central" {
350		t.Errorf("unexpected NetworkZone: %v", location.NetworkZone)
351	}
352}
353
354func TestServerFromSchema(t *testing.T) {
355	data := []byte(`{
356		"id": 1,
357		"name": "server.example.com",
358		"status": "running",
359		"created": "2017-08-16T17:29:14+00:00",
360		"public_net": {
361			"ipv4": {
362				"ip": "1.2.3.4",
363				"blocked": false,
364				"dns_ptr": "server01.example.com"
365			},
366			"ipv6": {
367				"ip": "2a01:4f8:1c11:3400::/64",
368				"blocked": false,
369				"dns_ptr": [
370					{
371						"ip": "2a01:4f8:1c11:3400::1/64",
372						"dns_ptr": "server01.example.com"
373					}
374				]
375			}
376		},
377		"private_net": [
378			{
379				"network": 4711,
380				"ip": "10.0.1.1",
381				"aliases": [
382					"10.0.1.2"
383				]
384			}
385		],
386		"server_type": {
387			"id": 2
388		},
389		"outgoing_traffic": 123456,
390		"ingoing_traffic": 7891011,
391		"included_traffic": 654321,
392		"backup_window": "22-02",
393		"rescue_enabled": true,
394		"primary_disk_size": 20,
395		"image": {
396			"id": 4711,
397			"type": "system",
398			"status": "available",
399			"name": "ubuntu16.04-standard-x64",
400			"description": "Ubuntu 16.04 Standard 64 bit",
401			"image_size": 2.3,
402			"disk_size": 10,
403			"created": "2017-08-16T17:29:14+00:00",
404			"created_from": {
405				"id": 1,
406				"name": "Server"
407			},
408			"bound_to": 1,
409			"os_flavor": "ubuntu",
410			"os_version": "16.04",
411			"rapid_deploy": false
412		},
413		"iso": {
414			"id": 4711,
415			"name": "FreeBSD-11.0-RELEASE-amd64-dvd1",
416			"description": "FreeBSD 11.0 x64",
417			"type": "public"
418		},
419		"datacenter": {
420			"id": 1,
421			"name": "fsn1-dc8",
422			"description": "Falkenstein 1 DC 8",
423			"location": {
424				"id": 1,
425				"name": "fsn1",
426				"description": "Falkenstein DC Park 1",
427				"country": "DE",
428				"city": "Falkenstein",
429				"latitude": 50.47612,
430				"longitude": 12.370071,
431				"network_zone": "eu-central"
432			}
433		},
434		"protection": {
435			"delete": true,
436			"rebuild": true
437		},
438		"locked": true,
439		"labels": {
440			"key": "value",
441			"key2": "value2"
442		},
443		"volumes": [123, 456, 789]
444	}`)
445
446	var s schema.Server
447	if err := json.Unmarshal(data, &s); err != nil {
448		t.Fatal(err)
449	}
450	server := ServerFromSchema(s)
451
452	if server.ID != 1 {
453		t.Errorf("unexpected ID: %v", server.ID)
454	}
455	if server.Name != "server.example.com" {
456		t.Errorf("unexpected name: %v", server.Name)
457	}
458	if server.Status != ServerStatusRunning {
459		t.Errorf("unexpected status: %v", server.Status)
460	}
461	if !server.Created.Equal(time.Date(2017, 8, 16, 17, 29, 14, 0, time.UTC)) {
462		t.Errorf("unexpected created date: %v", server.Created)
463	}
464	if server.PublicNet.IPv4.IP.String() != "1.2.3.4" {
465		t.Errorf("unexpected public net IPv4 IP: %v", server.PublicNet.IPv4.IP)
466	}
467	if server.ServerType.ID != 2 {
468		t.Errorf("unexpected server type ID: %v", server.ServerType.ID)
469	}
470	if server.IncludedTraffic != 654321 {
471		t.Errorf("unexpected included traffic: %v", server.IncludedTraffic)
472	}
473	if server.OutgoingTraffic != 123456 {
474		t.Errorf("unexpected outgoing traffic: %v", server.OutgoingTraffic)
475	}
476	if server.IngoingTraffic != 7891011 {
477		t.Errorf("unexpected ingoing traffic: %v", server.IngoingTraffic)
478	}
479	if server.BackupWindow != "22-02" {
480		t.Errorf("unexpected backup window: %v", server.BackupWindow)
481	}
482	if server.PrimaryDiskSize != 20 {
483		t.Errorf("unexpected primary disk size: %v", server.PrimaryDiskSize)
484	}
485	if !server.RescueEnabled {
486		t.Errorf("unexpected rescue enabled state: %v", server.RescueEnabled)
487	}
488	if server.Image == nil || server.Image.ID != 4711 {
489		t.Errorf("unexpected Image: %v", server.Image)
490	}
491	if server.ISO == nil || server.ISO.ID != 4711 {
492		t.Errorf("unexpected ISO: %v", server.ISO)
493	}
494	if server.Datacenter == nil || server.Datacenter.ID != 1 {
495		t.Errorf("unexpected Datacenter: %v", server.Datacenter)
496	}
497	if !server.Locked {
498		t.Errorf("unexpected value for Locked: %v", server.Locked)
499	}
500	if !server.Protection.Delete {
501		t.Errorf("unexpected value for Protection.Delete: %v", server.Protection.Delete)
502	}
503	if !server.Protection.Rebuild {
504		t.Errorf("unexpected value for Protection.Rebuild: %v", server.Protection.Rebuild)
505	}
506	if server.Labels["key"] != "value" || server.Labels["key2"] != "value2" {
507		t.Errorf("unexpected Labels: %v", server.Labels)
508	}
509	if len(s.Volumes) != 3 {
510		t.Errorf("unexpected number of volumes: %v", len(s.Volumes))
511	}
512	if s.Volumes[0] != 123 || s.Volumes[1] != 456 || s.Volumes[2] != 789 {
513		t.Errorf("unexpected volumes: %v", s.Volumes)
514	}
515	if len(server.PrivateNet) != 1 {
516		t.Errorf("unexpected length of PrivateNet: %v", len(server.PrivateNet))
517	}
518	if server.PrivateNet[0].Network.ID != 4711 {
519		t.Errorf("unexpected first private net: %v", server.PrivateNet[0])
520	}
521}
522
523func TestServerFromSchemaNoTraffic(t *testing.T) {
524	data := []byte(`{
525		"public_net": {
526			"ipv4": {
527				"ip": "1.2.3.4",
528				"blocked": false,
529				"dns_ptr": "server01.example.com"
530			},
531			"ipv6": {
532				"ip": "2a01:4f8:1c11:3400::/64",
533				"blocked": false,
534				"dns_ptr": [
535					{
536						"ip": "2a01:4f8:1c11:3400::1/64",
537						"dns_ptr": "server01.example.com"
538					}
539				]
540			}
541		},
542		"outgoing_traffic": null,
543		"ingoing_traffic": null
544	}`)
545
546	var s schema.Server
547	if err := json.Unmarshal(data, &s); err != nil {
548		t.Fatal(err)
549	}
550	server := ServerFromSchema(s)
551
552	if server.OutgoingTraffic != 0 {
553		t.Errorf("unexpected outgoing traffic: %v", server.OutgoingTraffic)
554	}
555	if server.IngoingTraffic != 0 {
556		t.Errorf("unexpected ingoing traffic: %v", server.IngoingTraffic)
557	}
558}
559
560func TestServerPublicNetFromSchema(t *testing.T) {
561	data := []byte(`{
562		"ipv4": {
563			"ip": "1.2.3.4",
564			"blocked": false,
565			"dns_ptr": "server.example.com"
566		},
567		"ipv6": {
568			"ip": "2a01:4f8:1c19:1403::/64",
569			"blocked": false,
570			"dns_ptr": []
571		},
572		"floating_ips": [4],
573		"firewalls": [
574			{
575				"id": 23,
576				"status": "applied"
577			}
578		]
579	}`)
580
581	var s schema.ServerPublicNet
582	if err := json.Unmarshal(data, &s); err != nil {
583		t.Fatal(err)
584	}
585	publicNet := ServerPublicNetFromSchema(s)
586
587	if publicNet.IPv4.IP.String() != "1.2.3.4" {
588		t.Errorf("unexpected IPv4 IP: %v", publicNet.IPv4.IP)
589	}
590	if publicNet.IPv6.Network.String() != "2a01:4f8:1c19:1403::/64" {
591		t.Errorf("unexpected IPv6 IP: %v", publicNet.IPv6.IP)
592	}
593	if len(publicNet.FloatingIPs) != 1 || publicNet.FloatingIPs[0].ID != 4 {
594		t.Errorf("unexpected Floating IPs: %v", publicNet.FloatingIPs)
595	}
596	if len(publicNet.Firewalls) != 1 || publicNet.Firewalls[0].Firewall.ID != 23 || publicNet.Firewalls[0].Status != FirewallStatusApplied {
597		t.Errorf("unexpected Firewalls: %v", publicNet.Firewalls)
598	}
599}
600
601func TestServerPublicNetIPv4FromSchema(t *testing.T) {
602	data := []byte(`{
603		"ip": "1.2.3.4",
604		"blocked": true,
605		"dns_ptr": "server.example.com"
606	}`)
607
608	var s schema.ServerPublicNetIPv4
609	if err := json.Unmarshal(data, &s); err != nil {
610		t.Fatal(err)
611	}
612	ipv4 := ServerPublicNetIPv4FromSchema(s)
613
614	if ipv4.IP.String() != "1.2.3.4" {
615		t.Errorf("unexpected IP: %v", ipv4.IP)
616	}
617	if !ipv4.Blocked {
618		t.Errorf("unexpected blocked state: %v", ipv4.Blocked)
619	}
620	if ipv4.DNSPtr != "server.example.com" {
621		t.Errorf("unexpected DNS ptr: %v", ipv4.DNSPtr)
622	}
623}
624
625func TestServerPublicNetIPv6FromSchema(t *testing.T) {
626	data := []byte(`{
627		"ip": "2a01:4f8:1c11:3400::/64",
628		"blocked": true,
629		"dns_ptr": [
630			{
631				"ip": "2a01:4f8:1c11:3400::1/64",
632				"blocked": "server01.example.com"
633			}
634		]
635	}`)
636
637	var s schema.ServerPublicNetIPv6
638	if err := json.Unmarshal(data, &s); err != nil {
639		t.Fatal(err)
640	}
641	ipv6 := ServerPublicNetIPv6FromSchema(s)
642
643	if ipv6.Network.String() != "2a01:4f8:1c11:3400::/64" {
644		t.Errorf("unexpected IP: %v", ipv6.IP)
645	}
646	if !ipv6.Blocked {
647		t.Errorf("unexpected blocked state: %v", ipv6.Blocked)
648	}
649	if len(ipv6.DNSPtr) != 1 {
650		t.Errorf("unexpected DNS ptr: %v", ipv6.DNSPtr)
651	}
652}
653
654func TestServerPrivateNetFromSchema(t *testing.T) {
655	data := []byte(`{
656		"network": 4711,
657		"ip": "10.0.1.1",
658		"alias_ips": [
659			"10.0.1.2"
660		],
661		"mac_address": "86:00:ff:2a:7d:e1"
662	}`)
663
664	var s schema.ServerPrivateNet
665	if err := json.Unmarshal(data, &s); err != nil {
666		t.Fatal(err)
667	}
668	privateNet := ServerPrivateNetFromSchema(s)
669
670	if privateNet.Network.ID != 4711 {
671		t.Errorf("unexpected Network: %v", privateNet.Network)
672	}
673	if privateNet.IP.String() != "10.0.1.1" {
674		t.Errorf("unexpected IP: %v", privateNet.IP)
675	}
676	if len(privateNet.Aliases) != 1 {
677		t.Errorf("unexpected number of alias IPs: %v", len(privateNet.Aliases))
678	}
679	if privateNet.Aliases[0].String() != "10.0.1.2" {
680		t.Errorf("unexpected alias IP: %v", privateNet.Aliases[0])
681	}
682	if privateNet.MACAddress != "86:00:ff:2a:7d:e1" {
683		t.Errorf("unexpected mac address: %v", privateNet.MACAddress)
684	}
685}
686
687func TestServerTypeFromSchema(t *testing.T) {
688	data := []byte(`{
689		"id": 1,
690		"name": "cx10",
691		"description": "description",
692		"cores": 4,
693		"memory": 1.0,
694		"disk": 20,
695		"storage_type": "local",
696		"cpu_type": "shared",
697		"prices": [
698			{
699				"location": "fsn1",
700				"price_hourly": {
701					"net": "1",
702					"gross": "1.19"
703				},
704				"price_monthly": {
705					"net": "1",
706					"gross": "1.19"
707				}
708			}
709		]
710	}`)
711
712	var s schema.ServerType
713	if err := json.Unmarshal(data, &s); err != nil {
714		t.Fatal(err)
715	}
716	serverType := ServerTypeFromSchema(s)
717
718	if serverType.ID != 1 {
719		t.Errorf("unexpected ID: %v", serverType.ID)
720	}
721	if serverType.Name != "cx10" {
722		t.Errorf("unexpected name: %q", serverType.Name)
723	}
724	if serverType.Description != "description" {
725		t.Errorf("unexpected description: %q", serverType.Description)
726	}
727	if serverType.Cores != 4 {
728		t.Errorf("unexpected cores: %v", serverType.Cores)
729	}
730	if serverType.Memory != 1.0 {
731		t.Errorf("unexpected memory: %v", serverType.Memory)
732	}
733	if serverType.Disk != 20 {
734		t.Errorf("unexpected disk: %v", serverType.Disk)
735	}
736	if serverType.StorageType != StorageTypeLocal {
737		t.Errorf("unexpected storage type: %q", serverType.StorageType)
738	}
739	if serverType.CPUType != CPUTypeShared {
740		t.Errorf("unexpected cpu type: %q", serverType.CPUType)
741	}
742	if len(serverType.Pricings) != 1 {
743		t.Errorf("unexpected number of pricings: %d", len(serverType.Pricings))
744	} else {
745		if serverType.Pricings[0].Location.Name != "fsn1" {
746			t.Errorf("unexpected location name: %v", serverType.Pricings[0].Location.Name)
747		}
748		if serverType.Pricings[0].Hourly.Net != "1" {
749			t.Errorf("unexpected hourly net price: %v", serverType.Pricings[0].Hourly.Net)
750		}
751		if serverType.Pricings[0].Hourly.Gross != "1.19" {
752			t.Errorf("unexpected hourly gross price: %v", serverType.Pricings[0].Hourly.Gross)
753		}
754		if serverType.Pricings[0].Monthly.Net != "1" {
755			t.Errorf("unexpected monthly net price: %v", serverType.Pricings[0].Monthly.Net)
756		}
757		if serverType.Pricings[0].Monthly.Gross != "1.19" {
758			t.Errorf("unexpected monthly gross price: %v", serverType.Pricings[0].Monthly.Gross)
759		}
760	}
761}
762
763func TestSSHKeyFromSchema(t *testing.T) {
764	data := []byte(`{
765		"id": 2323,
766		"name": "My key",
767		"fingerprint": "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2c",
768		"public_key": "ssh-rsa AAAjjk76kgf...Xt",
769		"labels": {
770			"key": "value",
771			"key2": "value2"
772		},
773		"created":"2017-08-16T17:29:14+00:00"
774	}`)
775
776	var s schema.SSHKey
777	if err := json.Unmarshal(data, &s); err != nil {
778		t.Fatal(err)
779	}
780	sshKey := SSHKeyFromSchema(s)
781
782	if sshKey.ID != 2323 {
783		t.Errorf("unexpected ID: %v", sshKey.ID)
784	}
785	if sshKey.Name != "My key" {
786		t.Errorf("unexpected name: %v", sshKey.Name)
787	}
788	if sshKey.Fingerprint != "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2c" {
789		t.Errorf("unexpected fingerprint: %v", sshKey.Fingerprint)
790	}
791	if sshKey.PublicKey != "ssh-rsa AAAjjk76kgf...Xt" {
792		t.Errorf("unexpected public key: %v", sshKey.PublicKey)
793	}
794	if sshKey.Labels["key"] != "value" || sshKey.Labels["key2"] != "value2" {
795		t.Errorf("unexpected labels: %v", sshKey.Labels)
796	}
797	if !sshKey.Created.Equal(time.Date(2017, 8, 16, 17, 29, 14, 0, time.UTC)) {
798		t.Errorf("unexpected created date: %v", sshKey.Created)
799	}
800}
801
802func TestErrorFromSchema(t *testing.T) {
803	t.Run("service_error", func(t *testing.T) {
804		data := []byte(`{
805			"code": "service_error",
806			"message": "An error occurred",
807			"details": {}
808		}`)
809
810		var s schema.Error
811		if err := json.Unmarshal(data, &s); err != nil {
812			t.Fatal(err)
813		}
814		err := ErrorFromSchema(s)
815
816		if err.Code != "service_error" {
817			t.Errorf("unexpected code: %v", err.Code)
818		}
819		if err.Message != "An error occurred" {
820			t.Errorf("unexpected message: %v", err.Message)
821		}
822	})
823
824	t.Run("invalid_input", func(t *testing.T) {
825		data := []byte(`{
826			"code": "invalid_input",
827			"message": "invalid input",
828			"details": {
829				"fields": [
830					{
831						"name": "broken_field",
832						"messages": ["is required"]
833					}
834				]
835			}
836		}`)
837
838		var s schema.Error
839		if err := json.Unmarshal(data, &s); err != nil {
840			t.Fatal(err)
841		}
842		err := ErrorFromSchema(s)
843
844		if err.Code != "invalid_input" {
845			t.Errorf("unexpected Code: %v", err.Code)
846		}
847		if err.Message != "invalid input" {
848			t.Errorf("unexpected Message: %v", err.Message)
849		}
850		if d, ok := err.Details.(ErrorDetailsInvalidInput); !ok {
851			t.Fatalf("unexpected Details type (should be ErrorDetailsInvalidInput): %v", err.Details)
852		} else {
853			if len(d.Fields) != 1 {
854				t.Fatalf("unexpected Details.Fields length (should be 1): %v", d.Fields)
855			}
856			if d.Fields[0].Name != "broken_field" {
857				t.Errorf("unexpected Details.Fields[0].Name: %v", d.Fields[0].Name)
858			}
859			if len(d.Fields[0].Messages) != 1 {
860				t.Fatalf("unexpected Details.Fields[0].Messages length (should be 1): %v", d.Fields[0].Messages)
861			}
862			if d.Fields[0].Messages[0] != "is required" {
863				t.Errorf("unexpected Details.Fields[0].Messages[0]: %v", d.Fields[0].Messages[0])
864			}
865		}
866	})
867}
868
869func TestPaginationFromSchema(t *testing.T) {
870	data := []byte(`{
871		"page": 2,
872		"per_page": 25,
873		"previous_page": 1,
874		"next_page": 3,
875		"last_page": 13,
876		"total_entries": 322
877	}`)
878
879	var s schema.MetaPagination
880	if err := json.Unmarshal(data, &s); err != nil {
881		t.Fatal(err)
882	}
883	p := PaginationFromSchema(s)
884
885	if p.Page != 2 {
886		t.Errorf("unexpected page: %v", p.Page)
887	}
888	if p.PerPage != 25 {
889		t.Errorf("unexpected per page: %v", p.PerPage)
890	}
891	if p.PreviousPage != 1 {
892		t.Errorf("unexpected previous page: %v", p.PreviousPage)
893	}
894	if p.NextPage != 3 {
895		t.Errorf("unexpected next page: %d", p.NextPage)
896	}
897	if p.LastPage != 13 {
898		t.Errorf("unexpected last page: %d", p.LastPage)
899	}
900	if p.TotalEntries != 322 {
901		t.Errorf("unexpected total entries: %d", p.TotalEntries)
902	}
903}
904
905func TestImageFromSchema(t *testing.T) {
906	data := []byte(`{
907		"id": 4711,
908		"type": "system",
909		"status": "available",
910		"name": "ubuntu16.04-standard-x64",
911		"description": "Ubuntu 16.04 Standard 64 bit",
912		"image_size": 2.3,
913		"disk_size": 10,
914		"created": "2016-01-30T23:55:01Z",
915		"created_from": {
916			"id": 1,
917			"name": "my-server1"
918		},
919		"bound_to": 1,
920		"os_flavor": "ubuntu",
921		"os_version": "16.04",
922		"rapid_deploy": false,
923		"protection": {
924			"delete": true
925		},
926		"deprecated": "2018-02-28T00:00:00+00:00",
927		"labels": {
928			"key": "value",
929			"key2": "value2"
930		}
931	}`)
932
933	var s schema.Image
934	if err := json.Unmarshal(data, &s); err != nil {
935		t.Fatal(err)
936	}
937	image := ImageFromSchema(s)
938
939	if image.ID != 4711 {
940		t.Errorf("unexpected ID: %v", image.ID)
941	}
942	if image.Type != ImageTypeSystem {
943		t.Errorf("unexpected Type: %v", image.Type)
944	}
945	if image.Status != ImageStatusAvailable {
946		t.Errorf("unexpected Status: %v", image.Status)
947	}
948	if image.Name != "ubuntu16.04-standard-x64" {
949		t.Errorf("unexpected Name: %v", image.Name)
950	}
951	if image.Description != "Ubuntu 16.04 Standard 64 bit" {
952		t.Errorf("unexpected Description: %v", image.Description)
953	}
954	if image.ImageSize != 2.3 {
955		t.Errorf("unexpected ImageSize: %v", image.ImageSize)
956	}
957	if image.DiskSize != 10 {
958		t.Errorf("unexpected DiskSize: %v", image.DiskSize)
959	}
960	if !image.Created.Equal(time.Date(2016, 1, 30, 23, 55, 1, 0, time.UTC)) {
961		t.Errorf("unexpected Created: %v", image.Created)
962	}
963	if image.CreatedFrom == nil || image.CreatedFrom.ID != 1 || image.CreatedFrom.Name != "my-server1" {
964		t.Errorf("unexpected CreatedFrom: %v", image.CreatedFrom)
965	}
966	if image.BoundTo == nil || image.BoundTo.ID != 1 {
967		t.Errorf("unexpected BoundTo: %v", image.BoundTo)
968	}
969	if image.OSVersion != "16.04" {
970		t.Errorf("unexpected OSVersion: %v", image.OSVersion)
971	}
972	if image.OSFlavor != "ubuntu" {
973		t.Errorf("unexpected OSFlavor: %v", image.OSFlavor)
974	}
975	if image.RapidDeploy {
976		t.Errorf("unexpected RapidDeploy: %v", image.RapidDeploy)
977	}
978	if !image.Protection.Delete {
979		t.Errorf("unexpected Protection.Delete: %v", image.Protection.Delete)
980	}
981	if image.Deprecated.IsZero() {
982		t.Errorf("unexpected value for Deprecated: %v", image.Deprecated)
983	}
984	if image.Labels["key"] != "value" || image.Labels["key2"] != "value2" {
985		t.Errorf("unexpected Labels: %v", image.Labels)
986	}
987}
988
989func TestVolumeFromSchema(t *testing.T) {
990	data := []byte(`{
991		"id": 4711,
992		"created": "2016-01-30T23:50:11+00:00",
993		"name": "db-storage",
994		"status": "creating",
995		"server": 2,
996		"location": {
997			"id": 1,
998			"name": "fsn1",
999			"description": "Falkenstein DC Park 1",
1000			"country": "DE",
1001			"city": "Falkenstein",
1002			"latitude": 50.47612,
1003			"longitude": 12.370071
1004		},
1005		"size": 42,
1006		"linux_device":"/dev/disk/by-id/scsi-0HC_volume_1",
1007		"protection": {
1008			"delete": true
1009		},
1010		"labels": {
1011			"key": "value",
1012			"key2": "value2"
1013		}
1014	}`)
1015	var s schema.Volume
1016	if err := json.Unmarshal(data, &s); err != nil {
1017		t.Fatal(err)
1018	}
1019	volume := VolumeFromSchema(s)
1020	if volume.ID != 4711 {
1021		t.Errorf("unexpected ID: %v", volume.ID)
1022	}
1023	if volume.Name != "db-storage" {
1024		t.Errorf("unexpected name: %v", volume.Name)
1025	}
1026	if volume.Status != VolumeStatusCreating {
1027		t.Errorf("unexpected status: %v", volume.Status)
1028	}
1029	if !volume.Created.Equal(time.Date(2016, 1, 30, 23, 50, 11, 0, time.UTC)) {
1030		t.Errorf("unexpected created date: %s", volume.Created)
1031	}
1032	if volume.Server == nil {
1033		t.Error("no server")
1034	}
1035	if volume.Server != nil && volume.Server.ID != 2 {
1036		t.Errorf("unexpected server ID: %v", volume.Server.ID)
1037	}
1038	if volume.Location == nil || volume.Location.ID != 1 {
1039		t.Errorf("unexpected location: %v", volume.Location)
1040	}
1041	if volume.Size != 42 {
1042		t.Errorf("unexpected size: %v", volume.Size)
1043	}
1044	if !volume.Protection.Delete {
1045		t.Errorf("unexpected value for delete protection: %v", volume.Protection.Delete)
1046	}
1047	if len(volume.Labels) != 2 {
1048		t.Errorf("unexpected number of labels: %d", len(volume.Labels))
1049	}
1050	if volume.Labels["key"] != "value" || volume.Labels["key2"] != "value2" {
1051		t.Errorf("unexpected labels: %v", volume.Labels)
1052	}
1053}
1054
1055func TestNetworkFromSchema(t *testing.T) {
1056	data := []byte(`{
1057		"id": 4711,
1058		"name": "mynet",
1059		"created": "2017-08-16T17:29:14+00:00",
1060		"ip_range": "10.0.0.0/16",
1061		"subnets": [
1062			{
1063				"type": "server",
1064				"ip_range": "10.0.1.0/24",
1065				"network_zone": "eu-central",
1066				"gateway": "10.0.0.1"
1067			}
1068		],
1069		"routes": [
1070			{
1071				"destination": "10.100.1.0/24",
1072				"gateway": "10.0.1.1"
1073			}
1074		],
1075		"servers": [
1076			4711
1077		],
1078		"protection": {
1079			"delete": false
1080		},
1081		"labels": {}
1082	}`)
1083
1084	var s schema.Network
1085	if err := json.Unmarshal(data, &s); err != nil {
1086		t.Fatal(err)
1087	}
1088	network := NetworkFromSchema(s)
1089	if network.ID != 4711 {
1090		t.Errorf("unexpected ID: %v", network.ID)
1091	}
1092	if network.Name != "mynet" {
1093		t.Errorf("unexpected Name: %v", network.Name)
1094	}
1095	if !network.Created.Equal(time.Date(2017, 8, 16, 17, 29, 14, 0, time.UTC)) {
1096		t.Errorf("unexpected created date: %v", network.Created)
1097	}
1098	if network.IPRange.String() != "10.0.0.0/16" {
1099		t.Errorf("unexpected IPRange: %v", network.IPRange)
1100	}
1101	if len(network.Subnets) != 1 {
1102		t.Errorf("unexpected length of Subnets: %v", len(network.Subnets))
1103	}
1104	if len(network.Routes) != 1 {
1105		t.Errorf("unexpected length of Routes: %v", len(network.Routes))
1106	}
1107	if len(network.Servers) != 1 {
1108		t.Errorf("unexpected length of Servers: %v", len(network.Servers))
1109	}
1110	if network.Servers[0].ID != 4711 {
1111		t.Errorf("unexpected Server ID: %v", network.Servers[0].ID)
1112	}
1113	if network.Protection.Delete {
1114		t.Errorf("unexpected value for delete protection: %v", network.Protection.Delete)
1115	}
1116}
1117
1118func TestNetworkSubnetFromSchema(t *testing.T) {
1119	t.Run("type server", func(t *testing.T) {
1120		data := []byte(`{
1121			"type": "server",
1122			"ip_range": "10.0.1.0/24",
1123			"network_zone": "eu-central",
1124			"gateway": "10.0.0.1"
1125		}`)
1126		var s schema.NetworkSubnet
1127		if err := json.Unmarshal(data, &s); err != nil {
1128			t.Fatal(err)
1129		}
1130		networkSubnet := NetworkSubnetFromSchema(s)
1131		if networkSubnet.NetworkZone != "eu-central" {
1132			t.Errorf("unexpected NetworkZone: %v", networkSubnet.NetworkZone)
1133		}
1134		if networkSubnet.Type != "server" {
1135			t.Errorf("unexpected Type: %v", networkSubnet.Type)
1136		}
1137		if networkSubnet.IPRange.String() != "10.0.1.0/24" {
1138			t.Errorf("unexpected IPRange: %v", networkSubnet.IPRange)
1139		}
1140		if networkSubnet.Gateway.String() != "10.0.0.1" {
1141			t.Errorf("unexpected Gateway: %v", networkSubnet.Gateway)
1142		}
1143		if networkSubnet.VSwitchID != 0 {
1144			t.Errorf("unexpected VSwitchID: %v", networkSubnet.VSwitchID)
1145		}
1146	})
1147
1148	t.Run("type vswitch", func(t *testing.T) {
1149		data := []byte(`{
1150			"type": "vswitch",
1151			"ip_range": "10.0.1.0/24",
1152			"network_zone": "eu-central",
1153			"gateway": "10.0.0.1",
1154			"vswitch_id": 123
1155		}`)
1156		var s schema.NetworkSubnet
1157		if err := json.Unmarshal(data, &s); err != nil {
1158			t.Fatal(err)
1159		}
1160		networkSubnet := NetworkSubnetFromSchema(s)
1161		if networkSubnet.NetworkZone != "eu-central" {
1162			t.Errorf("unexpected NetworkZone: %v", networkSubnet.NetworkZone)
1163		}
1164		if networkSubnet.Type != "vswitch" {
1165			t.Errorf("unexpected Type: %v", networkSubnet.Type)
1166		}
1167		if networkSubnet.IPRange.String() != "10.0.1.0/24" {
1168			t.Errorf("unexpected IPRange: %v", networkSubnet.IPRange)
1169		}
1170		if networkSubnet.Gateway.String() != "10.0.0.1" {
1171			t.Errorf("unexpected Gateway: %v", networkSubnet.Gateway)
1172		}
1173		if networkSubnet.VSwitchID != 123 {
1174			t.Errorf("unexpected VSwitchID: %v", networkSubnet.VSwitchID)
1175		}
1176	})
1177}
1178
1179func TestNetworkRouteFromSchema(t *testing.T) {
1180	data := []byte(`{
1181		"destination": "10.100.1.0/24",
1182		"gateway": "10.0.1.1"
1183	}`)
1184	var s schema.NetworkRoute
1185	if err := json.Unmarshal(data, &s); err != nil {
1186		t.Fatal(err)
1187	}
1188	networkRoute := NetworkRouteFromSchema(s)
1189	if networkRoute.Destination.String() != "10.100.1.0/24" {
1190		t.Errorf("unexpected Destination: %v", networkRoute.Destination)
1191	}
1192	if networkRoute.Gateway.String() != "10.0.1.1" {
1193		t.Errorf("unexpected Gateway: %v", networkRoute.Gateway)
1194	}
1195}
1196
1197func TestLoadBalancerTypeFromSchema(t *testing.T) {
1198	data := []byte(`{
1199		"id": 1,
1200		"name": "lx11",
1201		"description": "LX11",
1202		"max_connections": 20000,
1203		"max_services": 3,
1204		"max_targets": 25,
1205		"max_assigned_certificates": 10,
1206		"deprecated": "2016-01-30T23:50:00+00:00",
1207		"prices": [
1208			{
1209				"location": "fsn1",
1210				"price_hourly": {
1211					"net": "1",
1212					"gross": "1.19"
1213				},
1214				"price_monthly": {
1215					"net": "1",
1216					"gross": "1.19"
1217				}
1218			}
1219		]
1220	}`)
1221	var s schema.LoadBalancerType
1222	if err := json.Unmarshal(data, &s); err != nil {
1223		t.Fatal(err)
1224	}
1225	loadBalancerType := LoadBalancerTypeFromSchema(s)
1226	if loadBalancerType.ID != 1 {
1227		t.Errorf("unexpected ID: %v", loadBalancerType.ID)
1228	}
1229	if loadBalancerType.Name != "lx11" {
1230		t.Errorf("unexpected Name: %v", loadBalancerType.Name)
1231	}
1232	if loadBalancerType.Description != "LX11" {
1233		t.Errorf("unexpected Description: %v", loadBalancerType.Description)
1234	}
1235	if loadBalancerType.MaxConnections != 20000 {
1236		t.Errorf("unexpected MaxConnections: %v", loadBalancerType.MaxConnections)
1237	}
1238	if loadBalancerType.MaxServices != 3 {
1239		t.Errorf("unexpected MaxServices: %v", loadBalancerType.MaxServices)
1240	}
1241	if loadBalancerType.MaxTargets != 25 {
1242		t.Errorf("unexpected MaxTargets: %v", loadBalancerType.MaxTargets)
1243	}
1244	if loadBalancerType.MaxAssignedCertificates != 10 {
1245		t.Errorf("unexpected MaxAssignedCertificates: %v", loadBalancerType.MaxAssignedCertificates)
1246	}
1247	if len(loadBalancerType.Pricings) != 1 {
1248		t.Errorf("unexpected number of pricings: %d", len(loadBalancerType.Pricings))
1249	} else {
1250		if loadBalancerType.Pricings[0].Location.Name != "fsn1" {
1251			t.Errorf("unexpected location name: %v", loadBalancerType.Pricings[0].Location.Name)
1252		}
1253		if loadBalancerType.Pricings[0].Hourly.Net != "1" {
1254			t.Errorf("unexpected hourly net price: %v", loadBalancerType.Pricings[0].Hourly.Net)
1255		}
1256		if loadBalancerType.Pricings[0].Hourly.Gross != "1.19" {
1257			t.Errorf("unexpected hourly gross price: %v", loadBalancerType.Pricings[0].Hourly.Gross)
1258		}
1259		if loadBalancerType.Pricings[0].Monthly.Net != "1" {
1260			t.Errorf("unexpected monthly net price: %v", loadBalancerType.Pricings[0].Monthly.Net)
1261		}
1262		if loadBalancerType.Pricings[0].Monthly.Gross != "1.19" {
1263			t.Errorf("unexpected monthly gross price: %v", loadBalancerType.Pricings[0].Monthly.Gross)
1264		}
1265	}
1266}
1267
1268func TestLoadBalancerFromSchema(t *testing.T) {
1269	data := []byte(`{
1270		"id": 4711,
1271		"name": "Web Frontend",
1272		"public_net": {
1273			"ipv4": {
1274				"ip": "131.232.99.1"
1275			},
1276			"ipv6": {
1277				"ip": "2001:db8::1"
1278			}
1279		},
1280		"private_net": [
1281			{
1282				"network": 4711,
1283				"ip": "10.0.255.1"
1284			}
1285		],
1286		"location": {
1287			"id": 1,
1288			"name": "fsn1",
1289			"description": "Falkenstein DC Park 1",
1290			"country": "DE",
1291			"city": "Falkenstein",
1292			"latitude": 50.47612,
1293			"longitude": 12.370071,
1294			"network_zone": "eu-central"
1295		},
1296		"load_balancer_type": {
1297			"id": 1,
1298			"name": "lx11",
1299			"description": "LX11",
1300			"max_connections": 20000,
1301			"services": 3,
1302			"prices": [
1303				{
1304					"location": "fsn-1",
1305					"price_hourly": {
1306						"net": "1",
1307						"gross": "1.19"
1308					},
1309					"price_monthly": {
1310						"net": "1",
1311						"gross": "1.19"
1312					}
1313				}
1314			]
1315		},
1316		"outgoing_traffic": 123456,
1317		"ingoing_traffic": 7891011,
1318		"included_traffic": 654321,
1319		"protection": {
1320			"delete": false
1321		},
1322		"labels": {},
1323		"created": "2016-01-30T23:50:00+00:00",
1324		"services": [
1325			{
1326				"protocol": "http",
1327				"listen_port": 443,
1328				"destination_port": 80,
1329				"proxyprotocol": false,
1330				"sticky_sessions": false,
1331				"http": {
1332					"cookie_name": "HCLBSTICKY",
1333					"cookie_lifetime": 300,
1334					"certificates": [
1335						897
1336					]
1337				},
1338				"health_check": {
1339					"protocol": "http",
1340					"port": 4711,
1341					"interval": 15,
1342					"timeout": 10,
1343					"retries": 3,
1344					"http": {
1345						"domain": "example.com",
1346						"path": "/"
1347					}
1348				}
1349			}
1350		],
1351		"targets": [
1352			{
1353				"type": "server",
1354				"server": {
1355					"id": 80
1356				},
1357				"label_selector": null,
1358				"health_status": [
1359					{
1360						"listen_port": 443,
1361						"status": "healthy"
1362					}
1363				],
1364				"use_private_ip": false
1365			},
1366			{
1367				"type": "label_selector",
1368				"label_selector": {
1369					"selector": "lbt"
1370				},
1371				"targets": [
1372					{
1373						"type": "server",
1374						"server": {
1375							"id": 80
1376						},
1377						"health_status": [
1378							{
1379								"listen_port": 443,
1380								"status": "healthy"
1381							}
1382						],
1383						"use_private_ip": false
1384					}
1385				]
1386			}
1387		],
1388		"algorithm": {
1389			"type": "round_robin"
1390		}
1391	}`)
1392	var s schema.LoadBalancer
1393	if err := json.Unmarshal(data, &s); err != nil {
1394		t.Fatal(err)
1395	}
1396	loadBalancer := LoadBalancerFromSchema(s)
1397	if loadBalancer.ID != 4711 {
1398		t.Errorf("unexpected ID: %v", loadBalancer.ID)
1399	}
1400	if loadBalancer.Name != "Web Frontend" {
1401		t.Errorf("unexpected Name: %v", loadBalancer.Name)
1402	}
1403	if loadBalancer.PublicNet.IPv4.IP.String() != "131.232.99.1" {
1404		t.Errorf("unexpected IPv4: %v", loadBalancer.PublicNet.IPv4.IP)
1405	}
1406	if loadBalancer.PublicNet.IPv6.IP.String() != "2001:db8::1" {
1407		t.Errorf("unexpected IPv6: %v", loadBalancer.PublicNet.IPv6)
1408	}
1409	if len(loadBalancer.PrivateNet) != 1 {
1410		t.Errorf("unexpected length of PrivateNet: %v", len(loadBalancer.PrivateNet))
1411	} else {
1412		if loadBalancer.PrivateNet[0].Network.ID != 4711 {
1413			t.Errorf("unexpected Network ID: %v", loadBalancer.PrivateNet[0].Network.ID)
1414		}
1415		if loadBalancer.PrivateNet[0].IP.String() != "10.0.255.1" {
1416			t.Errorf("unexpected Network IP: %v", loadBalancer.PrivateNet[0].IP)
1417		}
1418	}
1419	if loadBalancer.Location == nil || loadBalancer.Location.ID != 1 {
1420		t.Errorf("unexpected Location: %v", loadBalancer.Location)
1421	}
1422	if loadBalancer.LoadBalancerType == nil || loadBalancer.LoadBalancerType.ID != 1 {
1423		t.Errorf("unexpected LoadBalancerType: %v", loadBalancer.LoadBalancerType)
1424	}
1425	if loadBalancer.Protection.Delete {
1426		t.Errorf("unexpected value for delete protection: %v", loadBalancer.Protection.Delete)
1427	}
1428	if !loadBalancer.Created.Equal(time.Date(2016, 01, 30, 23, 50, 00, 0, time.UTC)) {
1429		t.Errorf("unexpected created date: %v", loadBalancer.Created)
1430	}
1431	if len(loadBalancer.Services) != 1 {
1432		t.Errorf("unexpected length of Services: %v", len(loadBalancer.Services))
1433	}
1434	if len(loadBalancer.Targets) != 2 {
1435		t.Errorf("unexpected length of Targets: %v", len(loadBalancer.Targets))
1436	}
1437	if loadBalancer.Algorithm.Type != "round_robin" {
1438		t.Errorf("unexpected Algorithm.Type: %v", loadBalancer.Algorithm.Type)
1439	}
1440	if loadBalancer.IncludedTraffic != 654321 {
1441		t.Errorf("unexpected included traffic: %v", loadBalancer.IncludedTraffic)
1442	}
1443	if loadBalancer.OutgoingTraffic != 123456 {
1444		t.Errorf("unexpected outgoing traffic: %v", loadBalancer.OutgoingTraffic)
1445	}
1446	if loadBalancer.IngoingTraffic != 7891011 {
1447		t.Errorf("unexpected ingoing traffic: %v", loadBalancer.IngoingTraffic)
1448	}
1449}
1450
1451func TestLoadBalancerServiceFromSchema(t *testing.T) {
1452	data := []byte(`{
1453		"protocol": "http",
1454		"listen_port": 443,
1455		"destination_port": 80,
1456		"proxyprotocol": false,
1457		"http": {
1458			"cookie_name": "HCLBSTICKY",
1459			"cookie_lifetime": 300,
1460			"certificates": [
1461				897
1462			],
1463			"redirect_http": true,
1464			"sticky_sessions": true
1465		},
1466		"health_check": {
1467			"protocol": "http",
1468			"port": 4711,
1469			"interval": 15,
1470			"timeout": 10,
1471			"retries": 3,
1472			"http": {
1473				"domain": "example.com",
1474				"path": "/",
1475				"response": "",
1476				"status_codes":["200","201"],
1477				"tls": false
1478			}
1479		}
1480	}`)
1481	var s schema.LoadBalancerService
1482	if err := json.Unmarshal(data, &s); err != nil {
1483		t.Fatal(err)
1484	}
1485	loadBalancerService := LoadBalancerServiceFromSchema(s)
1486	if loadBalancerService.Protocol != "http" {
1487		t.Errorf("unexpected Protocol: %v", loadBalancerService.Protocol)
1488	}
1489	if loadBalancerService.ListenPort != 443 {
1490		t.Errorf("unexpected ListenPort: %v", loadBalancerService.ListenPort)
1491	}
1492	if loadBalancerService.DestinationPort != 80 {
1493		t.Errorf("unexpected DestinationPort: %v", loadBalancerService.DestinationPort)
1494	}
1495	if loadBalancerService.Proxyprotocol {
1496		t.Errorf("unexpected ProxyProtocol: %v", loadBalancerService.Proxyprotocol)
1497	}
1498	if loadBalancerService.HTTP.CookieName != "HCLBSTICKY" {
1499		t.Errorf("unexpected HTTP.CookieName: %v", loadBalancerService.HTTP.CookieName)
1500	}
1501	if loadBalancerService.HTTP.CookieLifetime.Seconds() != 300 {
1502		t.Errorf("unexpected HTTP.CookieLifetime: %v", loadBalancerService.HTTP.CookieLifetime.Seconds())
1503	}
1504	if loadBalancerService.HTTP.Certificates[0].ID != 897 {
1505		t.Errorf("unexpected Certificates[0].ID : %v", loadBalancerService.HTTP.Certificates[0].ID)
1506	}
1507	if !loadBalancerService.HTTP.RedirectHTTP {
1508		t.Errorf("unexpected HTTP.RedirectHTTP: %v", loadBalancerService.HTTP.RedirectHTTP)
1509	}
1510
1511	if !loadBalancerService.HTTP.StickySessions {
1512		t.Errorf("unexpected HTTP.StickySessions: %v", loadBalancerService.HTTP.StickySessions)
1513	}
1514	if loadBalancerService.HealthCheck.Protocol != "http" {
1515		t.Errorf("unexpected HealthCheck.Protocol: %v", loadBalancerService.HealthCheck.Protocol)
1516	}
1517	if loadBalancerService.HealthCheck.Port != 4711 {
1518		t.Errorf("unexpected HealthCheck.Port: %v", loadBalancerService.HealthCheck.Port)
1519	}
1520	if loadBalancerService.HealthCheck.Interval.Seconds() != 15 {
1521		t.Errorf("unexpected HealthCheck.Interval: %v", loadBalancerService.HealthCheck.Interval)
1522	}
1523	if loadBalancerService.HealthCheck.Timeout.Seconds() != 10 {
1524		t.Errorf("unexpected HealthCheck.Timeout: %v", loadBalancerService.HealthCheck.Timeout)
1525	}
1526	if loadBalancerService.HealthCheck.Retries != 3 {
1527		t.Errorf("unexpected HealthCheck.Retries: %v", loadBalancerService.HealthCheck.Retries)
1528	}
1529	if loadBalancerService.HealthCheck.HTTP.Domain != "example.com" {
1530		t.Errorf("unexpected HealthCheck.HTTP.Domain: %v", loadBalancerService.HealthCheck.HTTP.Domain)
1531	}
1532	if loadBalancerService.HealthCheck.HTTP.Path != "/" {
1533		t.Errorf("unexpected HealthCheck.HTTP.Path: %v", loadBalancerService.HealthCheck.HTTP.Path)
1534	}
1535	if loadBalancerService.HealthCheck.HTTP.Response != "" {
1536		t.Errorf("unexpected HealthCheck.HTTP.Response: %v", loadBalancerService.HealthCheck.HTTP.Response)
1537	}
1538	if loadBalancerService.HealthCheck.HTTP.TLS {
1539		t.Errorf("unexpected HealthCheck.HTTP.TLS: %v", loadBalancerService.HealthCheck.HTTP.TLS)
1540	}
1541	if len(loadBalancerService.HealthCheck.HTTP.StatusCodes) != 2 {
1542		t.Errorf("unexpected len(HealthCheck.HTTP.StatusCodes): %v", len(loadBalancerService.HealthCheck.HTTP.StatusCodes))
1543	} else {
1544		if loadBalancerService.HealthCheck.HTTP.StatusCodes[0] != "200" {
1545			t.Errorf("unexpected HealthCheck.HTTP.StatusCodes[0]: %v", loadBalancerService.HealthCheck.HTTP.StatusCodes[0])
1546		}
1547		if loadBalancerService.HealthCheck.HTTP.StatusCodes[1] != "201" {
1548			t.Errorf("unexpected HealthCheck.HTTP.StatusCodes[1]: %v", loadBalancerService.HealthCheck.HTTP.StatusCodes[1])
1549		}
1550	}
1551}
1552
1553func TestLoadBalancerTargetFromSchema(t *testing.T) {
1554	t.Run("server target", func(t *testing.T) {
1555		data := []byte(`{
1556		"type": "server",
1557		"server": {
1558			"id": 80
1559		},
1560		"label_selector": null,
1561		"health_status": [
1562			{
1563				"listen_port": 443,
1564				"status": "healthy"
1565			}
1566		],
1567		"use_private_ip": false
1568	}`)
1569		var s schema.LoadBalancerTarget
1570		if err := json.Unmarshal(data, &s); err != nil {
1571			t.Fatal(err)
1572		}
1573		loadBalancerTarget := LoadBalancerTargetFromSchema(s)
1574		if loadBalancerTarget.Type != "server" {
1575			t.Errorf("unexpected Type: %v", loadBalancerTarget.Type)
1576		}
1577		if loadBalancerTarget.Server == nil || loadBalancerTarget.Server.Server.ID != 80 {
1578			t.Errorf("unexpected Server: %v", loadBalancerTarget.Server)
1579		}
1580		if loadBalancerTarget.LabelSelector != nil {
1581			t.Errorf("unexpected LabelSelector.Selector: %v", loadBalancerTarget.LabelSelector)
1582		}
1583		if loadBalancerTarget.UsePrivateIP {
1584			t.Errorf("unexpected UsePrivateIP: %v", loadBalancerTarget.UsePrivateIP)
1585		}
1586		if len(loadBalancerTarget.HealthStatus) != 1 {
1587			t.Errorf("unexpected Health Status length: %v", len(loadBalancerTarget.HealthStatus))
1588		} else {
1589			if loadBalancerTarget.HealthStatus[0].ListenPort != 443 {
1590				t.Errorf("unexpected HealthStatus[0].ListenPort: %v", loadBalancerTarget.HealthStatus[0].ListenPort)
1591			}
1592			if loadBalancerTarget.HealthStatus[0].Status != LoadBalancerTargetHealthStatusStatusHealthy {
1593				t.Errorf("unexpected HealthStatus[0].Status: %v", loadBalancerTarget.HealthStatus[0].Status)
1594			}
1595		}
1596	})
1597	t.Run("label_selector target", func(t *testing.T) {
1598		data := []byte(`{
1599			"type": "label_selector",
1600			"label_selector": {
1601				"selector": "lbt"
1602			},
1603			"targets": [
1604				{
1605					"type": "server",
1606					"server": {
1607						"id": 80
1608					},
1609					"health_status": [
1610						{
1611							"listen_port": 443,
1612							"status": "healthy"
1613						}
1614					]
1615				}
1616			]
1617		}`)
1618		var s schema.LoadBalancerTarget
1619		if err := json.Unmarshal(data, &s); err != nil {
1620			t.Fatal(err)
1621		}
1622		loadBalancerTarget := LoadBalancerTargetFromSchema(s)
1623		if loadBalancerTarget.Type != "label_selector" {
1624			t.Errorf("unexpected Type: %v", loadBalancerTarget.Type)
1625		}
1626		if loadBalancerTarget.LabelSelector == nil || loadBalancerTarget.LabelSelector.Selector != "lbt" {
1627			t.Errorf("unexpected LabelSelector: %v", loadBalancerTarget.LabelSelector)
1628		}
1629		if loadBalancerTarget.Server != nil {
1630			t.Errorf("unexpected LabelSelector.Server: %v", loadBalancerTarget.Server)
1631		}
1632		if len(loadBalancerTarget.Targets) != 1 {
1633			t.Errorf("unexpected Targets length: %v", len(loadBalancerTarget.Targets))
1634		} else {
1635			if loadBalancerTarget.Targets[0].Server == nil || loadBalancerTarget.Targets[0].Server.Server.ID != 80 {
1636				t.Errorf("unexpected loadBalancerTarget.Targets[0].Server.Server.ID: %v", loadBalancerTarget.Targets[0].Server.Server.ID)
1637			}
1638			if len(loadBalancerTarget.Targets[0].HealthStatus) != 1 {
1639				t.Errorf("unexpected Targets length: %v", len(loadBalancerTarget.Targets[0].HealthStatus))
1640			} else {
1641				if loadBalancerTarget.Targets[0].HealthStatus[0].ListenPort != 443 {
1642					t.Errorf("unexpected HealthStatus[0].ListenPort: %v", loadBalancerTarget.Targets[0].HealthStatus[0].ListenPort)
1643				}
1644				if loadBalancerTarget.Targets[0].HealthStatus[0].Status != LoadBalancerTargetHealthStatusStatusHealthy {
1645					t.Errorf("unexpected HealthStatus[0].Status: %v", loadBalancerTarget.Targets[0].HealthStatus[0].Status)
1646				}
1647			}
1648		}
1649	})
1650
1651	t.Run("ip target", func(t *testing.T) {
1652		var s schema.LoadBalancerTarget
1653
1654		data := []byte(`{
1655			"type": "ip",
1656			"ip": {
1657				"ip": "1.2.3.4"
1658			}
1659		}`)
1660		if err := json.Unmarshal(data, &s); err != nil {
1661			t.Fatal(err)
1662		}
1663		lbTgt := LoadBalancerTargetFromSchema(s)
1664		if lbTgt.Type != LoadBalancerTargetTypeIP {
1665			t.Errorf("unexpected Type: %s", lbTgt.Type)
1666		}
1667		if lbTgt.IP.IP != "1.2.3.4" {
1668			t.Errorf("unexpected IP: %s", lbTgt.IP.IP)
1669		}
1670	})
1671}
1672
1673func TestCertificateFromSchema(t *testing.T) {
1674	data := []byte(`{
1675		"id": 897,
1676		"name": "my website cert",
1677		"labels": {},
1678		"certificate": "-----BEGIN CERTIFICATE-----\n...",
1679		"created": "2016-01-30T23:50:00+00:00",
1680		"not_valid_before": "2016-01-30T23:51:00+00:00",
1681		"not_valid_after": "2016-01-30T23:55:00+00:00",
1682		"domain_names": [
1683			"example.com",
1684			"webmail.example.com",
1685			"www.example.com"
1686		],
1687		"fingerprint": "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f",
1688		"used_by": [
1689			{
1690			    "id": 42,
1691			    "type": "server"
1692			}
1693		]
1694	}
1695`)
1696	var s schema.Certificate
1697	if err := json.Unmarshal(data, &s); err != nil {
1698		t.Fatal(err)
1699	}
1700	certificate := CertificateFromSchema(s)
1701
1702	if certificate.ID != 897 {
1703		t.Errorf("unexpected ID: %v", certificate.ID)
1704	}
1705	if certificate.Name != "my website cert" {
1706		t.Errorf("unexpected Name: %v", certificate.Name)
1707	}
1708	if certificate.Certificate != "-----BEGIN CERTIFICATE-----\n..." {
1709		t.Errorf("unexpected Certificate: %v", certificate.Certificate)
1710	}
1711	if !certificate.Created.Equal(time.Date(2016, 01, 30, 23, 50, 00, 0, time.UTC)) {
1712		t.Errorf("unexpected created date: %v", certificate.Created)
1713	}
1714	if !certificate.NotValidBefore.Equal(time.Date(2016, 01, 30, 23, 51, 00, 0, time.UTC)) {
1715		t.Errorf("unexpected NotValidBefore: %v", certificate.NotValidBefore)
1716	}
1717	if !certificate.NotValidAfter.Equal(time.Date(2016, 01, 30, 23, 55, 00, 0, time.UTC)) {
1718		t.Errorf("unexpected NotValidAfter: %v", certificate.NotValidAfter)
1719	}
1720	if len(certificate.DomainNames) != 3 {
1721		t.Errorf("unexpected DomainNames length: %v", len(certificate.DomainNames))
1722	} else {
1723		if certificate.DomainNames[0] != "example.com" {
1724			t.Errorf("unexpected DomainNames[0]: %v", certificate.DomainNames[0])
1725		}
1726		if certificate.DomainNames[1] != "webmail.example.com" {
1727			t.Errorf("unexpected DomainNames[1]: %v", certificate.DomainNames[1])
1728		}
1729		if certificate.DomainNames[2] != "www.example.com" {
1730			t.Errorf("unexpected DomainNames[2]: %v", certificate.DomainNames[2])
1731		}
1732	}
1733	if certificate.Fingerprint != "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f" {
1734		t.Errorf("unexpected Fingerprint: %v", certificate.Fingerprint)
1735	}
1736}
1737
1738func TestPricingFromSchema(t *testing.T) {
1739	data := []byte(`{
1740		"currency": "EUR",
1741		"vat_rate": "19.00",
1742		"image": {
1743			"price_per_gb_month": {
1744				"net": "1",
1745				"gross": "1.19"
1746			}
1747		},
1748		"floating_ip": {
1749			"price_monthly": {
1750				"net": "1",
1751				"gross": "1.19"
1752			}
1753		},
1754		"traffic": {
1755			"price_per_tb": {
1756				"net": "1",
1757				"gross": "1.19"
1758			}
1759		},
1760		"server_backup": {
1761			"percentage": "20"
1762		},
1763		"server_types": [
1764			{
1765				"id": 4,
1766				"name": "CX11",
1767				"prices": [
1768					{
1769						"location": "fsn1",
1770						"price_hourly": {
1771							"net": "1",
1772							"gross": "1.19"
1773						},
1774						"price_monthly": {
1775							"net": "1",
1776							"gross": "1.19"
1777						}
1778					}
1779				]
1780			}
1781		],
1782		"load_balancer_types": [
1783			{
1784				"id": 4,
1785				"name": "LX11",
1786				"prices": [
1787					{
1788						"location": "fsn1",
1789						"price_hourly": {
1790							"net": "1",
1791							"gross": "1.19"
1792						},
1793						"price_monthly": {
1794							"net": "1",
1795							"gross": "1.19"
1796						}
1797					}
1798				]
1799			}
1800		],
1801		"volume": {
1802			"price_per_gb_month": {
1803				"net": "1",
1804				"gross": "1.19"
1805			}
1806		}
1807	}`)
1808
1809	var s schema.Pricing
1810	if err := json.Unmarshal(data, &s); err != nil {
1811		t.Fatal(err)
1812	}
1813	pricing := PricingFromSchema(s)
1814
1815	if pricing.Image.PerGBMonth.Currency != "EUR" {
1816		t.Errorf("unexpected Image.PerGBMonth.Currency: %v", pricing.Image.PerGBMonth.Currency)
1817	}
1818	if pricing.Image.PerGBMonth.VATRate != "19.00" {
1819		t.Errorf("unexpected Image.PerGBMonth.VATRate: %v", pricing.Image.PerGBMonth.VATRate)
1820	}
1821	if pricing.Image.PerGBMonth.Net != "1" {
1822		t.Errorf("unexpected Image.PerGBMonth.Net: %v", pricing.Image.PerGBMonth.Net)
1823	}
1824	if pricing.Image.PerGBMonth.Gross != "1.19" {
1825		t.Errorf("unexpected Image.PerGBMonth.Gross: %v", pricing.Image.PerGBMonth.Gross)
1826	}
1827
1828	if pricing.FloatingIP.Monthly.Currency != "EUR" {
1829		t.Errorf("unexpected FloatingIP.Monthly.Currency: %v", pricing.FloatingIP.Monthly.Currency)
1830	}
1831	if pricing.FloatingIP.Monthly.VATRate != "19.00" {
1832		t.Errorf("unexpected FloatingIP.Monthly.VATRate: %v", pricing.FloatingIP.Monthly.VATRate)
1833	}
1834	if pricing.FloatingIP.Monthly.Net != "1" {
1835		t.Errorf("unexpected FloatingIP.Monthly.Net: %v", pricing.FloatingIP.Monthly.Net)
1836	}
1837	if pricing.FloatingIP.Monthly.Gross != "1.19" {
1838		t.Errorf("unexpected FloatingIP.Monthly.Gross: %v", pricing.FloatingIP.Monthly.Gross)
1839	}
1840
1841	if pricing.Volume.PerGBMonthly.Currency != "EUR" {
1842		t.Errorf("unexpected Traffic.PerTB.Currency: %v", pricing.Volume.PerGBMonthly.Currency)
1843	}
1844	if pricing.Volume.PerGBMonthly.VATRate != "19.00" {
1845		t.Errorf("unexpected Traffic.PerTB.VATRate: %v", pricing.Volume.PerGBMonthly.VATRate)
1846	}
1847	if pricing.Volume.PerGBMonthly.Net != "1" {
1848		t.Errorf("unexpected Traffic.PerTB.Net: %v", pricing.Volume.PerGBMonthly.Net)
1849	}
1850	if pricing.Volume.PerGBMonthly.Gross != "1.19" {
1851		t.Errorf("unexpected Traffic.PerTB.Gross: %v", pricing.Volume.PerGBMonthly.Gross)
1852	}
1853
1854	if pricing.Traffic.PerTB.Currency != "EUR" {
1855		t.Errorf("unexpected Traffic.PerTB.Currency: %v", pricing.Traffic.PerTB.Currency)
1856	}
1857	if pricing.Traffic.PerTB.VATRate != "19.00" {
1858		t.Errorf("unexpected Traffic.PerTB.VATRate: %v", pricing.Traffic.PerTB.VATRate)
1859	}
1860	if pricing.Traffic.PerTB.Net != "1" {
1861		t.Errorf("unexpected Traffic.PerTB.Net: %v", pricing.Traffic.PerTB.Net)
1862	}
1863	if pricing.Traffic.PerTB.Gross != "1.19" {
1864		t.Errorf("unexpected Traffic.PerTB.Gross: %v", pricing.Traffic.PerTB.Gross)
1865	}
1866
1867	if pricing.ServerBackup.Percentage != "20" {
1868		t.Errorf("unexpected ServerBackup.Percentage: %v", pricing.ServerBackup.Percentage)
1869	}
1870
1871	if len(pricing.ServerTypes) != 1 {
1872		t.Errorf("unexpected number of server types: %d", len(pricing.ServerTypes))
1873	} else {
1874		p := pricing.ServerTypes[0]
1875
1876		if p.ServerType.ID != 4 {
1877			t.Errorf("unexpected ServerType.ID: %d", p.ServerType.ID)
1878		}
1879		if p.ServerType.Name != "CX11" {
1880			t.Errorf("unexpected ServerType.Name: %v", p.ServerType.Name)
1881		}
1882
1883		if len(p.Pricings) != 1 {
1884			t.Errorf("unexpected number of prices: %d", len(p.Pricings))
1885		} else {
1886			if p.Pricings[0].Location.Name != "fsn1" {
1887				t.Errorf("unexpected Location.Name: %v", p.Pricings[0].Location.Name)
1888			}
1889
1890			if p.Pricings[0].Hourly.Currency != "EUR" {
1891				t.Errorf("unexpected Hourly.Currency: %v", p.Pricings[0].Hourly.Currency)
1892			}
1893			if p.Pricings[0].Hourly.VATRate != "19.00" {
1894				t.Errorf("unexpected Hourly.VATRate: %v", p.Pricings[0].Hourly.VATRate)
1895			}
1896			if p.Pricings[0].Hourly.Net != "1" {
1897				t.Errorf("unexpected Hourly.Net: %v", p.Pricings[0].Hourly.Net)
1898			}
1899			if p.Pricings[0].Hourly.Gross != "1.19" {
1900				t.Errorf("unexpected Hourly.Gross: %v", p.Pricings[0].Hourly.Gross)
1901			}
1902
1903			if p.Pricings[0].Monthly.Currency != "EUR" {
1904				t.Errorf("unexpected Monthly.Currency: %v", p.Pricings[0].Monthly.Currency)
1905			}
1906			if p.Pricings[0].Monthly.VATRate != "19.00" {
1907				t.Errorf("unexpected Monthly.VATRate: %v", p.Pricings[0].Monthly.VATRate)
1908			}
1909			if p.Pricings[0].Monthly.Net != "1" {
1910				t.Errorf("unexpected Monthly.Net: %v", p.Pricings[0].Monthly.Net)
1911			}
1912			if p.Pricings[0].Monthly.Gross != "1.19" {
1913				t.Errorf("unexpected Monthly.Gross: %v", p.Pricings[0].Monthly.Gross)
1914			}
1915		}
1916	}
1917
1918	if len(pricing.LoadBalancerTypes) != 1 {
1919		t.Errorf("unexpected number of Load Balancer types: %d", len(pricing.LoadBalancerTypes))
1920	} else {
1921		p := pricing.LoadBalancerTypes[0]
1922
1923		if p.LoadBalancerType.ID != 4 {
1924			t.Errorf("unexpected LoadBalancerType.ID: %d", p.LoadBalancerType.ID)
1925		}
1926		if p.LoadBalancerType.Name != "LX11" {
1927			t.Errorf("unexpected LoadBalancerType.Name: %v", p.LoadBalancerType.Name)
1928		}
1929
1930		if len(p.Pricings) != 1 {
1931			t.Errorf("unexpected number of prices: %d", len(p.Pricings))
1932		} else {
1933			if p.Pricings[0].Location.Name != "fsn1" {
1934				t.Errorf("unexpected Location.Name: %v", p.Pricings[0].Location.Name)
1935			}
1936
1937			if p.Pricings[0].Hourly.Currency != "EUR" {
1938				t.Errorf("unexpected Hourly.Currency: %v", p.Pricings[0].Hourly.Currency)
1939			}
1940			if p.Pricings[0].Hourly.VATRate != "19.00" {
1941				t.Errorf("unexpected Hourly.VATRate: %v", p.Pricings[0].Hourly.VATRate)
1942			}
1943			if p.Pricings[0].Hourly.Net != "1" {
1944				t.Errorf("unexpected Hourly.Net: %v", p.Pricings[0].Hourly.Net)
1945			}
1946			if p.Pricings[0].Hourly.Gross != "1.19" {
1947				t.Errorf("unexpected Hourly.Gross: %v", p.Pricings[0].Hourly.Gross)
1948			}
1949
1950			if p.Pricings[0].Monthly.Currency != "EUR" {
1951				t.Errorf("unexpected Monthly.Currency: %v", p.Pricings[0].Monthly.Currency)
1952			}
1953			if p.Pricings[0].Monthly.VATRate != "19.00" {
1954				t.Errorf("unexpected Monthly.VATRate: %v", p.Pricings[0].Monthly.VATRate)
1955			}
1956			if p.Pricings[0].Monthly.Net != "1" {
1957				t.Errorf("unexpected Monthly.Net: %v", p.Pricings[0].Monthly.Net)
1958			}
1959			if p.Pricings[0].Monthly.Gross != "1.19" {
1960				t.Errorf("unexpected Monthly.Gross: %v", p.Pricings[0].Monthly.Gross)
1961			}
1962		}
1963	}
1964}
1965
1966func TestLoadBalancerCreateOptsToSchema(t *testing.T) {
1967	testCases := map[string]struct {
1968		Opts    LoadBalancerCreateOpts
1969		Request schema.LoadBalancerCreateRequest
1970	}{
1971		"minimal": {
1972			Opts: LoadBalancerCreateOpts{
1973				Name:             "test",
1974				LoadBalancerType: &LoadBalancerType{Name: "lb11"},
1975				Algorithm:        &LoadBalancerAlgorithm{Type: LoadBalancerAlgorithmTypeRoundRobin},
1976				NetworkZone:      NetworkZoneEUCentral,
1977			},
1978			Request: schema.LoadBalancerCreateRequest{
1979				Name:             "test",
1980				LoadBalancerType: "lb11",
1981				Algorithm: &schema.LoadBalancerCreateRequestAlgorithm{
1982					Type: string(LoadBalancerAlgorithmTypeRoundRobin),
1983				},
1984				NetworkZone: String(string(NetworkZoneEUCentral)),
1985			},
1986		},
1987		"all set": {
1988			Opts: LoadBalancerCreateOpts{
1989				Name:             "test",
1990				LoadBalancerType: &LoadBalancerType{Name: "lb11"},
1991				Algorithm:        &LoadBalancerAlgorithm{Type: LoadBalancerAlgorithmTypeRoundRobin},
1992				NetworkZone:      NetworkZoneEUCentral,
1993				Labels:           map[string]string{"foo": "bar"},
1994				PublicInterface:  Bool(true),
1995				Network:          &Network{ID: 3},
1996				Services: []LoadBalancerCreateOptsService{
1997					{
1998						Protocol:        LoadBalancerServiceProtocolHTTP,
1999						DestinationPort: Int(80),
2000						Proxyprotocol:   Bool(true),
2001						HTTP: &LoadBalancerCreateOptsServiceHTTP{
2002							CookieName:     String("keks"),
2003							CookieLifetime: Duration(5 * time.Minute),
2004							RedirectHTTP:   Bool(true),
2005							StickySessions: Bool(true),
2006							Certificates:   []*Certificate{{ID: 1}, {ID: 2}},
2007						},
2008						HealthCheck: &LoadBalancerCreateOptsServiceHealthCheck{
2009							Protocol: LoadBalancerServiceProtocolHTTP,
2010							Port:     Int(80),
2011							Interval: Duration(5 * time.Second),
2012							Timeout:  Duration(1 * time.Second),
2013							Retries:  Int(3),
2014							HTTP: &LoadBalancerCreateOptsServiceHealthCheckHTTP{
2015								Domain:      String("example.com"),
2016								Path:        String("/health"),
2017								Response:    String("ok"),
2018								StatusCodes: []string{"2??", "3??"},
2019								TLS:         Bool(true),
2020							},
2021						},
2022					},
2023				},
2024				Targets: []LoadBalancerCreateOptsTarget{
2025					{
2026						Type: LoadBalancerTargetTypeServer,
2027						Server: LoadBalancerCreateOptsTargetServer{
2028							Server: &Server{ID: 5},
2029						},
2030					},
2031					{
2032						Type: LoadBalancerTargetTypeIP,
2033						IP:   LoadBalancerCreateOptsTargetIP{IP: "1.2.3.4"},
2034					},
2035				},
2036			},
2037			Request: schema.LoadBalancerCreateRequest{
2038				Name:             "test",
2039				LoadBalancerType: "lb11",
2040				Algorithm: &schema.LoadBalancerCreateRequestAlgorithm{
2041					Type: string(LoadBalancerAlgorithmTypeRoundRobin),
2042				},
2043				NetworkZone: String(string(NetworkZoneEUCentral)),
2044				Labels: func() *map[string]string {
2045					labels := map[string]string{"foo": "bar"}
2046					return &labels
2047				}(),
2048				PublicInterface: Bool(true),
2049				Network:         Int(3),
2050				Services: []schema.LoadBalancerCreateRequestService{
2051					{
2052						Protocol:        string(LoadBalancerServiceProtocolHTTP),
2053						DestinationPort: Int(80),
2054						Proxyprotocol:   Bool(true),
2055						HTTP: &schema.LoadBalancerCreateRequestServiceHTTP{
2056							CookieName:     String("keks"),
2057							CookieLifetime: Int(5 * 60),
2058							RedirectHTTP:   Bool(true),
2059							StickySessions: Bool(true),
2060							Certificates:   intSlice([]int{1, 2}),
2061						},
2062						HealthCheck: &schema.LoadBalancerCreateRequestServiceHealthCheck{
2063							Protocol: string(LoadBalancerServiceProtocolHTTP),
2064							Port:     Int(80),
2065							Interval: Int(5),
2066							Timeout:  Int(1),
2067							Retries:  Int(3),
2068							HTTP: &schema.LoadBalancerCreateRequestServiceHealthCheckHTTP{
2069								Domain:      String("example.com"),
2070								Path:        String("/health"),
2071								Response:    String("ok"),
2072								StatusCodes: stringSlice([]string{"2??", "3??"}),
2073								TLS:         Bool(true),
2074							},
2075						},
2076					},
2077				},
2078				Targets: []schema.LoadBalancerCreateRequestTarget{
2079					{
2080						Type: "server",
2081						Server: &schema.LoadBalancerCreateRequestTargetServer{
2082							ID: 5,
2083						},
2084					},
2085					{
2086						Type: "ip",
2087						IP: &schema.LoadBalancerCreateRequestTargetIP{
2088							IP: "1.2.3.4",
2089						},
2090					},
2091				},
2092			},
2093		},
2094	}
2095	for name, testCase := range testCases {
2096		t.Run(name, func(t *testing.T) {
2097			req := loadBalancerCreateOptsToSchema(testCase.Opts)
2098			if !cmp.Equal(testCase.Request, req) {
2099				t.Log(cmp.Diff(testCase.Request, req))
2100				t.Fail()
2101			}
2102		})
2103	}
2104}
2105
2106func TestLoadBalancerAddServiceOptsToSchema(t *testing.T) {
2107	testCases := map[string]struct {
2108		Opts    LoadBalancerAddServiceOpts
2109		Request schema.LoadBalancerActionAddServiceRequest
2110	}{
2111		"minimal": {
2112			Opts: LoadBalancerAddServiceOpts{
2113				Protocol: LoadBalancerServiceProtocolHTTP,
2114			},
2115			Request: schema.LoadBalancerActionAddServiceRequest{
2116				Protocol: string(LoadBalancerServiceProtocolHTTP),
2117			},
2118		},
2119		"all set": {
2120			Opts: LoadBalancerAddServiceOpts{
2121				Protocol:        LoadBalancerServiceProtocolHTTP,
2122				DestinationPort: Int(80),
2123				Proxyprotocol:   Bool(true),
2124				HTTP: &LoadBalancerAddServiceOptsHTTP{
2125					CookieName:     String("keks"),
2126					CookieLifetime: Duration(5 * time.Minute),
2127					RedirectHTTP:   Bool(true),
2128					StickySessions: Bool(true),
2129					Certificates:   []*Certificate{{ID: 1}, {ID: 2}},
2130				},
2131				HealthCheck: &LoadBalancerAddServiceOptsHealthCheck{
2132					Protocol: LoadBalancerServiceProtocolHTTP,
2133					Port:     Int(80),
2134					Interval: Duration(5 * time.Second),
2135					Timeout:  Duration(1 * time.Second),
2136					Retries:  Int(3),
2137					HTTP: &LoadBalancerAddServiceOptsHealthCheckHTTP{
2138						Domain:      String("example.com"),
2139						Path:        String("/health"),
2140						Response:    String("ok"),
2141						StatusCodes: []string{"2??", "3??"},
2142						TLS:         Bool(true),
2143					},
2144				},
2145			},
2146			Request: schema.LoadBalancerActionAddServiceRequest{
2147				Protocol:        string(LoadBalancerServiceProtocolHTTP),
2148				DestinationPort: Int(80),
2149				Proxyprotocol:   Bool(true),
2150				HTTP: &schema.LoadBalancerActionAddServiceRequestHTTP{
2151					CookieName:     String("keks"),
2152					CookieLifetime: Int(5 * 60),
2153					RedirectHTTP:   Bool(true),
2154					StickySessions: Bool(true),
2155					Certificates:   intSlice([]int{1, 2}),
2156				},
2157				HealthCheck: &schema.LoadBalancerActionAddServiceRequestHealthCheck{
2158					Protocol: string(LoadBalancerServiceProtocolHTTP),
2159					Port:     Int(80),
2160					Interval: Int(5),
2161					Timeout:  Int(1),
2162					Retries:  Int(3),
2163					HTTP: &schema.LoadBalancerActionAddServiceRequestHealthCheckHTTP{
2164						Domain:      String("example.com"),
2165						Path:        String("/health"),
2166						Response:    String("ok"),
2167						StatusCodes: stringSlice([]string{"2??", "3??"}),
2168						TLS:         Bool(true),
2169					},
2170				},
2171			},
2172		},
2173		"no health check": {
2174			Opts: LoadBalancerAddServiceOpts{
2175				Protocol:        LoadBalancerServiceProtocolHTTP,
2176				DestinationPort: Int(80),
2177				Proxyprotocol:   Bool(true),
2178				HTTP: &LoadBalancerAddServiceOptsHTTP{
2179					CookieName:     String("keks"),
2180					CookieLifetime: Duration(5 * time.Minute),
2181					RedirectHTTP:   Bool(true),
2182					StickySessions: Bool(true),
2183					Certificates:   []*Certificate{{ID: 1}, {ID: 2}},
2184				},
2185			},
2186			Request: schema.LoadBalancerActionAddServiceRequest{
2187				Protocol:        string(LoadBalancerServiceProtocolHTTP),
2188				DestinationPort: Int(80),
2189				Proxyprotocol:   Bool(true),
2190				HTTP: &schema.LoadBalancerActionAddServiceRequestHTTP{
2191					CookieName:     String("keks"),
2192					CookieLifetime: Int(5 * 60),
2193					RedirectHTTP:   Bool(true),
2194					StickySessions: Bool(true),
2195					Certificates:   intSlice([]int{1, 2}),
2196				},
2197				HealthCheck: nil,
2198			},
2199		},
2200	}
2201	for name, testCase := range testCases {
2202		t.Run(name, func(t *testing.T) {
2203			req := loadBalancerAddServiceOptsToSchema(testCase.Opts)
2204			if !cmp.Equal(testCase.Request, req) {
2205				t.Log(cmp.Diff(testCase.Request, req))
2206				t.Fail()
2207			}
2208		})
2209	}
2210}
2211
2212func TestLoadBalancerUpdateServiceOptsToSchema(t *testing.T) {
2213	testCases := map[string]struct {
2214		Opts    LoadBalancerUpdateServiceOpts
2215		Request schema.LoadBalancerActionUpdateServiceRequest
2216	}{
2217		"empty": {
2218			Opts:    LoadBalancerUpdateServiceOpts{},
2219			Request: schema.LoadBalancerActionUpdateServiceRequest{},
2220		},
2221		"all set": {
2222			Opts: LoadBalancerUpdateServiceOpts{
2223				Protocol:        LoadBalancerServiceProtocolHTTP,
2224				DestinationPort: Int(80),
2225				Proxyprotocol:   Bool(true),
2226				HTTP: &LoadBalancerUpdateServiceOptsHTTP{
2227					CookieName:     String("keks"),
2228					CookieLifetime: Duration(5 * time.Minute),
2229					RedirectHTTP:   Bool(true),
2230					StickySessions: Bool(true),
2231					Certificates:   []*Certificate{{ID: 1}, {ID: 2}},
2232				},
2233				HealthCheck: &LoadBalancerUpdateServiceOptsHealthCheck{
2234					Protocol: LoadBalancerServiceProtocolHTTP,
2235					Port:     Int(80),
2236					Interval: Duration(5 * time.Second),
2237					Timeout:  Duration(1 * time.Second),
2238					Retries:  Int(3),
2239					HTTP: &LoadBalancerUpdateServiceOptsHealthCheckHTTP{
2240						Domain:      String("example.com"),
2241						Path:        String("/health"),
2242						Response:    String("ok"),
2243						StatusCodes: []string{"2??", "3??"},
2244						TLS:         Bool(true),
2245					},
2246				},
2247			},
2248			Request: schema.LoadBalancerActionUpdateServiceRequest{
2249				Protocol:        String(string(LoadBalancerServiceProtocolHTTP)),
2250				DestinationPort: Int(80),
2251				Proxyprotocol:   Bool(true),
2252				HTTP: &schema.LoadBalancerActionUpdateServiceRequestHTTP{
2253					CookieName:     String("keks"),
2254					CookieLifetime: Int(5 * 60),
2255					RedirectHTTP:   Bool(true),
2256					StickySessions: Bool(true),
2257					Certificates:   intSlice([]int{1, 2}),
2258				},
2259				HealthCheck: &schema.LoadBalancerActionUpdateServiceRequestHealthCheck{
2260					Protocol: String(string(LoadBalancerServiceProtocolHTTP)),
2261					Port:     Int(80),
2262					Interval: Int(5),
2263					Timeout:  Int(1),
2264					Retries:  Int(3),
2265					HTTP: &schema.LoadBalancerActionUpdateServiceRequestHealthCheckHTTP{
2266						Domain:      String("example.com"),
2267						Path:        String("/health"),
2268						Response:    String("ok"),
2269						StatusCodes: stringSlice([]string{"2??", "3??"}),
2270						TLS:         Bool(true),
2271					},
2272				},
2273			},
2274		},
2275		"no health check": {
2276			Opts: LoadBalancerUpdateServiceOpts{
2277				Protocol:        LoadBalancerServiceProtocolHTTP,
2278				DestinationPort: Int(80),
2279				Proxyprotocol:   Bool(true),
2280				HTTP: &LoadBalancerUpdateServiceOptsHTTP{
2281					CookieName:     String("keks"),
2282					CookieLifetime: Duration(5 * time.Minute),
2283					RedirectHTTP:   Bool(true),
2284					StickySessions: Bool(true),
2285					Certificates:   []*Certificate{{ID: 1}, {ID: 2}},
2286				},
2287			},
2288			Request: schema.LoadBalancerActionUpdateServiceRequest{
2289				Protocol:        String(string(LoadBalancerServiceProtocolHTTP)),
2290				DestinationPort: Int(80),
2291				Proxyprotocol:   Bool(true),
2292				HTTP: &schema.LoadBalancerActionUpdateServiceRequestHTTP{
2293					CookieName:     String("keks"),
2294					CookieLifetime: Int(5 * 60),
2295					RedirectHTTP:   Bool(true),
2296					StickySessions: Bool(true),
2297					Certificates:   intSlice([]int{1, 2}),
2298				},
2299				HealthCheck: nil,
2300			},
2301		},
2302	}
2303	for name, testCase := range testCases {
2304		t.Run(name, func(t *testing.T) {
2305			req := loadBalancerUpdateServiceOptsToSchema(testCase.Opts)
2306			if !cmp.Equal(testCase.Request, req) {
2307				t.Log(cmp.Diff(testCase.Request, req))
2308				t.Fail()
2309			}
2310		})
2311	}
2312}
2313
2314func TestServerMetricsFromSchema(t *testing.T) {
2315	tests := []struct {
2316		name        string
2317		respFn      func() *schema.ServerGetMetricsResponse
2318		expected    *ServerMetrics
2319		expectedErr string
2320	}{
2321		{
2322			name: "values not tuples",
2323			respFn: func() *schema.ServerGetMetricsResponse {
2324				var resp schema.ServerGetMetricsResponse
2325
2326				resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
2327				resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
2328				resp.Metrics.TimeSeries = map[string]schema.ServerTimeSeriesVals{
2329					"cpu": {
2330						Values: []interface{}{"some value"},
2331					},
2332				}
2333
2334				return &resp
2335			},
2336			expectedErr: "failed to convert value to tuple: some value",
2337		},
2338		{
2339			name: "invalid tuple size",
2340			respFn: func() *schema.ServerGetMetricsResponse {
2341				var resp schema.ServerGetMetricsResponse
2342
2343				resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
2344				resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
2345				resp.Metrics.TimeSeries = map[string]schema.ServerTimeSeriesVals{
2346					"cpu": {
2347						Values: []interface{}{
2348							[]interface{}{1435781471.622, "43", "something else"},
2349						},
2350					},
2351				}
2352
2353				return &resp
2354			},
2355			expectedErr: "invalid tuple size: 3: [1.435781471622e+09 43 something else]",
2356		},
2357		{
2358			name: "invalid time stamp",
2359			respFn: func() *schema.ServerGetMetricsResponse {
2360				var resp schema.ServerGetMetricsResponse
2361
2362				resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
2363				resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
2364				resp.Metrics.TimeSeries = map[string]schema.ServerTimeSeriesVals{
2365					"cpu": {
2366						Values: []interface{}{
2367							[]interface{}{"1435781471.622", "43"},
2368						},
2369					},
2370				}
2371
2372				return &resp
2373			},
2374			expectedErr: "convert to float64: 1435781471.622",
2375		},
2376		{
2377			name: "invalid value",
2378			respFn: func() *schema.ServerGetMetricsResponse {
2379				var resp schema.ServerGetMetricsResponse
2380
2381				resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
2382				resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
2383				resp.Metrics.TimeSeries = map[string]schema.ServerTimeSeriesVals{
2384					"cpu": {
2385						Values: []interface{}{
2386							[]interface{}{1435781471.622, 43},
2387						},
2388					},
2389				}
2390
2391				return &resp
2392			},
2393			expectedErr: "not a string: 43",
2394		},
2395		{
2396			name: "valid response",
2397			respFn: func() *schema.ServerGetMetricsResponse {
2398				var resp schema.ServerGetMetricsResponse
2399
2400				resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
2401				resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
2402				resp.Metrics.TimeSeries = map[string]schema.ServerTimeSeriesVals{
2403					"cpu": {
2404						Values: []interface{}{
2405							[]interface{}{1435781470.622, "42"},
2406							[]interface{}{1435781471.622, "43"},
2407						},
2408					},
2409					"disk.0.iops.read": {
2410						Values: []interface{}{
2411							[]interface{}{1435781480.622, "100"},
2412							[]interface{}{1435781481.622, "150"},
2413						},
2414					},
2415					"disk.0.iops.write": {
2416						Values: []interface{}{
2417							[]interface{}{1435781480.622, "50"},
2418							[]interface{}{1435781481.622, "55"},
2419						},
2420					},
2421					"network.0.pps.in": {
2422						Values: []interface{}{
2423							[]interface{}{1435781490.622, "70"},
2424							[]interface{}{1435781491.622, "75"},
2425						},
2426					},
2427					"network.0.pps.out": {
2428						Values: []interface{}{
2429							[]interface{}{1435781590.622, "60"},
2430							[]interface{}{1435781591.622, "65"},
2431						},
2432					},
2433				}
2434
2435				return &resp
2436			},
2437			expected: &ServerMetrics{
2438				Start: mustParseTime(t, "2017-01-01T00:00:00Z"),
2439				End:   mustParseTime(t, "2017-01-01T23:00:00Z"),
2440				TimeSeries: map[string][]ServerMetricsValue{
2441					"cpu": {
2442						{Timestamp: 1435781470.622, Value: "42"},
2443						{Timestamp: 1435781471.622, Value: "43"},
2444					},
2445					"disk.0.iops.read": {
2446						{Timestamp: 1435781480.622, Value: "100"},
2447						{Timestamp: 1435781481.622, Value: "150"},
2448					},
2449					"disk.0.iops.write": {
2450						{Timestamp: 1435781480.622, Value: "50"},
2451						{Timestamp: 1435781481.622, Value: "55"},
2452					},
2453					"network.0.pps.in": {
2454						{Timestamp: 1435781490.622, Value: "70"},
2455						{Timestamp: 1435781491.622, Value: "75"},
2456					},
2457					"network.0.pps.out": {
2458						{Timestamp: 1435781590.622, Value: "60"},
2459						{Timestamp: 1435781591.622, Value: "65"},
2460					},
2461				},
2462			},
2463		},
2464	}
2465
2466	for _, tt := range tests {
2467		tt := tt
2468		t.Run(tt.name, func(t *testing.T) {
2469			resp := tt.respFn()
2470			actual, err := serverMetricsFromSchema(resp)
2471			if err != nil && tt.expectedErr == "" {
2472				t.Fatalf("expected no error; got: %v", err)
2473			}
2474			if err != nil && tt.expectedErr != err.Error() {
2475				t.Fatalf("expected error: %s; got: %v", tt.expectedErr, err)
2476			}
2477			if !cmp.Equal(tt.expected, actual) {
2478				t.Errorf("unexpected result:\n%s", cmp.Diff(tt.expected, actual))
2479			}
2480		})
2481	}
2482}
2483
2484func TestLoadBalancerMetricsFromSchema(t *testing.T) {
2485	tests := []struct {
2486		name        string
2487		respFn      func() *schema.LoadBalancerGetMetricsResponse
2488		expected    *LoadBalancerMetrics
2489		expectedErr string
2490	}{
2491		{
2492			name: "values not tuples",
2493			respFn: func() *schema.LoadBalancerGetMetricsResponse {
2494				var resp schema.LoadBalancerGetMetricsResponse
2495
2496				resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
2497				resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
2498				resp.Metrics.TimeSeries = map[string]schema.LoadBalancerTimeSeriesVals{
2499					"open_connections": {
2500						Values: []interface{}{"some value"},
2501					},
2502				}
2503
2504				return &resp
2505			},
2506			expectedErr: "failed to convert value to tuple: some value",
2507		},
2508		{
2509			name: "invalid tuple size",
2510			respFn: func() *schema.LoadBalancerGetMetricsResponse {
2511				var resp schema.LoadBalancerGetMetricsResponse
2512
2513				resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
2514				resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
2515				resp.Metrics.TimeSeries = map[string]schema.LoadBalancerTimeSeriesVals{
2516					"open_connections": {
2517						Values: []interface{}{
2518							[]interface{}{1435781471.622, "43", "something else"},
2519						},
2520					},
2521				}
2522
2523				return &resp
2524			},
2525			expectedErr: "invalid tuple size: 3: [1.435781471622e+09 43 something else]",
2526		},
2527		{
2528			name: "invalid time stamp",
2529			respFn: func() *schema.LoadBalancerGetMetricsResponse {
2530				var resp schema.LoadBalancerGetMetricsResponse
2531
2532				resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
2533				resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
2534				resp.Metrics.TimeSeries = map[string]schema.LoadBalancerTimeSeriesVals{
2535					"open_connections": {
2536						Values: []interface{}{
2537							[]interface{}{"1435781471.622", "43"},
2538						},
2539					},
2540				}
2541
2542				return &resp
2543			},
2544			expectedErr: "convert to float64: 1435781471.622",
2545		},
2546		{
2547			name: "invalid value",
2548			respFn: func() *schema.LoadBalancerGetMetricsResponse {
2549				var resp schema.LoadBalancerGetMetricsResponse
2550
2551				resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
2552				resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
2553				resp.Metrics.TimeSeries = map[string]schema.LoadBalancerTimeSeriesVals{
2554					"open_connections": {
2555						Values: []interface{}{
2556							[]interface{}{1435781471.622, 43},
2557						},
2558					},
2559				}
2560
2561				return &resp
2562			},
2563			expectedErr: "not a string: 43",
2564		},
2565		{
2566			name: "valid response",
2567			respFn: func() *schema.LoadBalancerGetMetricsResponse {
2568				var resp schema.LoadBalancerGetMetricsResponse
2569
2570				resp.Metrics.Start = mustParseTime(t, "2017-01-01T00:00:00Z")
2571				resp.Metrics.End = mustParseTime(t, "2017-01-01T23:00:00Z")
2572				resp.Metrics.TimeSeries = map[string]schema.LoadBalancerTimeSeriesVals{
2573					"open_connections": {
2574						Values: []interface{}{
2575							[]interface{}{1435781470.622, "42"},
2576							[]interface{}{1435781471.622, "43"},
2577						},
2578					},
2579					"connections_per_second": {
2580						Values: []interface{}{
2581							[]interface{}{1435781480.622, "100"},
2582							[]interface{}{1435781481.622, "150"},
2583						},
2584					},
2585					"requests_per_second": {
2586						Values: []interface{}{
2587							[]interface{}{1435781480.622, "50"},
2588							[]interface{}{1435781481.622, "55"},
2589						},
2590					},
2591					"bandwidth.in": {
2592						Values: []interface{}{
2593							[]interface{}{1435781490.622, "70"},
2594							[]interface{}{1435781491.622, "75"},
2595						},
2596					},
2597					"bandwidth.out": {
2598						Values: []interface{}{
2599							[]interface{}{1435781590.622, "60"},
2600							[]interface{}{1435781591.622, "65"},
2601						},
2602					},
2603				}
2604
2605				return &resp
2606			},
2607			expected: &LoadBalancerMetrics{
2608				Start: mustParseTime(t, "2017-01-01T00:00:00Z"),
2609				End:   mustParseTime(t, "2017-01-01T23:00:00Z"),
2610				TimeSeries: map[string][]LoadBalancerMetricsValue{
2611					"open_connections": {
2612						{Timestamp: 1435781470.622, Value: "42"},
2613						{Timestamp: 1435781471.622, Value: "43"},
2614					},
2615					"connections_per_second": {
2616						{Timestamp: 1435781480.622, Value: "100"},
2617						{Timestamp: 1435781481.622, Value: "150"},
2618					},
2619					"requests_per_second": {
2620						{Timestamp: 1435781480.622, Value: "50"},
2621						{Timestamp: 1435781481.622, Value: "55"},
2622					},
2623					"bandwidth.in": {
2624						{Timestamp: 1435781490.622, Value: "70"},
2625						{Timestamp: 1435781491.622, Value: "75"},
2626					},
2627					"bandwidth.out": {
2628						{Timestamp: 1435781590.622, Value: "60"},
2629						{Timestamp: 1435781591.622, Value: "65"},
2630					},
2631				},
2632			},
2633		},
2634	}
2635
2636	for _, tt := range tests {
2637		tt := tt
2638		t.Run(tt.name, func(t *testing.T) {
2639			resp := tt.respFn()
2640			actual, err := loadBalancerMetricsFromSchema(resp)
2641			if err != nil && tt.expectedErr == "" {
2642				t.Fatalf("expected no error; got: %v", err)
2643			}
2644			if err != nil && tt.expectedErr != err.Error() {
2645				t.Fatalf("expected error: %s; got: %v", tt.expectedErr, err)
2646			}
2647			if !cmp.Equal(tt.expected, actual) {
2648				t.Errorf("unexpected result:\n%s", cmp.Diff(tt.expected, actual))
2649			}
2650		})
2651	}
2652}
2653
2654func TestFirewallFromSchema(t *testing.T) {
2655	data := []byte(`{
2656		"id": 897,
2657		"name": "my firewall",
2658		"labels": {
2659			"key": "value",
2660			"key2": "value2"
2661		},
2662		"created": "2016-01-30T23:50:00+00:00",
2663		"rules": [
2664			{
2665			  "direction": "in",
2666			  "source_ips": [
2667				"28.239.13.1/32",
2668				"28.239.14.0/24",
2669				"ff21:1eac:9a3b:ee58:5ca:990c:8bc9:c03b/128"
2670			  ],
2671			  "destination_ips": [
2672				"28.239.13.1/32",
2673				"28.239.14.0/24",
2674				"ff21:1eac:9a3b:ee58:5ca:990c:8bc9:c03b/128"
2675			  ],
2676			  "protocol": "tcp",
2677			  "port": "80"
2678			}
2679		],
2680		"applied_to": [
2681			{
2682			 	"server": {
2683					"id": 42
2684				},
2685				"type": "server"
2686			}
2687		  ]
2688	}
2689`)
2690	var f schema.Firewall
2691	if err := json.Unmarshal(data, &f); err != nil {
2692		t.Fatal(err)
2693	}
2694	firewall := FirewallFromSchema(f)
2695
2696	if firewall.ID != 897 {
2697		t.Errorf("unexpected ID: %v", firewall.ID)
2698	}
2699	if firewall.Name != "my firewall" {
2700		t.Errorf("unexpected Name: %v", firewall.Name)
2701	}
2702	if firewall.Labels["key"] != "value" || firewall.Labels["key2"] != "value2" {
2703		t.Errorf("unexpected Labels: %v", firewall.Labels)
2704	}
2705	if !firewall.Created.Equal(time.Date(2016, 01, 30, 23, 50, 00, 0, time.UTC)) {
2706		t.Errorf("unexpected Created date: %v", firewall.Created)
2707	}
2708	if len(firewall.Rules) != 1 {
2709		t.Errorf("unexpected Rules count: %d", len(firewall.Rules))
2710	}
2711	if firewall.Rules[0].Direction != FirewallRuleDirectionIn {
2712		t.Errorf("unexpected Rule Direction: %s", firewall.Rules[0].Direction)
2713	}
2714	if len(firewall.Rules[0].SourceIPs) != 3 {
2715		t.Errorf("unexpected Rule SourceIPs count: %d", len(firewall.Rules[0].SourceIPs))
2716	}
2717	if len(firewall.Rules[0].DestinationIPs) != 3 {
2718		t.Errorf("unexpected Rule DestinationIPs count: %d", len(firewall.Rules[0].DestinationIPs))
2719	}
2720	if firewall.Rules[0].Protocol != FirewallRuleProtocolTCP {
2721		t.Errorf("unexpected Rule Protocol: %s", firewall.Rules[0].Protocol)
2722	}
2723	if *firewall.Rules[0].Port != "80" {
2724		t.Errorf("unexpected Rule Port: %s", *firewall.Rules[0].Port)
2725	}
2726	if len(firewall.AppliedTo) != 1 {
2727		t.Errorf("unexpected UsedBy count: %d", len(firewall.AppliedTo))
2728	}
2729	if firewall.AppliedTo[0].Type != FirewallResourceTypeServer {
2730		t.Errorf("unexpected UsedBy Type: %s", firewall.AppliedTo[0].Type)
2731	}
2732	if firewall.AppliedTo[0].Server.ID != 42 {
2733		t.Errorf("unexpected UsedBy Server ID: %d", firewall.AppliedTo[0].Server.ID)
2734	}
2735}
2736