github.com/amimof/huego@v1.2.1/huego_test.go (about)

     1  package huego
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"testing"
     8  
     9  	"github.com/jarcoal/httpmock"
    10  	"github.com/stretchr/testify/assert"
    11  )
    12  
    13  // I'm too lazy to have this elsewhere
    14  // export HUE_USERNAME=9D6iHMbM-Bt7Kd0Cwh9Quo4tE02FMnmrNrnFAdAq
    15  // export HUE_HOSTNAME=192.168.1.59
    16  
    17  var username string
    18  var hostname string
    19  var badHostname = "bad-hue-config"
    20  
    21  func init() {
    22  
    23  	hostname = os.Getenv("HUE_HOSTNAME")
    24  	username = os.Getenv("HUE_USERNAME")
    25  
    26  	tests := []struct {
    27  		method string
    28  		path   string
    29  		data   string
    30  		url    string
    31  	}{
    32  		// DISCOVERY
    33  		{
    34  			method: "GET",
    35  			url:    "https://discovery.meethue.com",
    36  			data:   `[{"id":"001788fffe73ff19","internalipaddress":"192.168.13.112"}]`,
    37  		},
    38  
    39  		// CONFIG
    40  		{
    41  			method: "GET",
    42  			path:   "/config",
    43  			data:   `{"name":"Philipshue","zigbeechannel":15,"mac":"00:17:88:00:00:00","dhcp":true,"ipaddress":"192.168.1.7","netmask":"255.255.255.0","gateway":"192.168.1.1","proxyaddress":"none","proxyport":0,"UTC":"2014-07-17T09:27:35","localtime":"2014-07-17T11:27:35","timezone":"Europe/Madrid","whitelist":{"ffffffffe0341b1b376a2389376a2389":{"lastusedate":"2014-07-17T07:21:38","createdate":"2014-04-08T08:55:10","name":"PhilipsHueAndroidApp#TCTALCATELONETOU"},"pAtwdCV8NZId25Gk":{"lastusedate":"2014-05-07T18:28:29","createdate":"2014-04-09T17:29:16","name":"MyApplication"},"gDN3IaPYSYNPWa2H":{"lastusedate":"2014-05-07T09:15:21","createdate":"2014-05-07T09:14:38","name":"iPhoneWeb1"}},"swversion":"01012917","apiversion":"1.3.0","swupdate":{"updatestate":2,"checkforupdate":false,"devicetypes":{"bridge":true,"lights":["1","2","3"], "sensors":["4","5","6"]},"url":"","text":"010000000","notify":false},"linkbutton":false,"portalservices":false,"portalconnection":"connected","portalstate":{"signedon":true,"incoming":false,"outgoing":true,"communication":"disconnected"}}`,
    44  		},
    45  		{
    46  			method: "PUT",
    47  			path:   "/config",
    48  			data:   `[{"success":{"/config/name":"My bridge"}}]`,
    49  		},
    50  		{
    51  			method: "GET",
    52  			path:   path.Join("/invalid_password", "/lights"),
    53  			data:   `[{"error":{"type":1,"address":"/lights","description":"unauthorized user"}}]`,
    54  		},
    55  		{
    56  			method: "GET",
    57  			path:   "",
    58  			data:   `{"lights":{"1":{"state":{"on":false,"bri":0,"hue":0,"sat":0,"xy":[0.0000,0.0000],"ct":0,"alert":"none","effect":"none","colormode":"hs","reachable":true},"type":"Extendedcolorlight","name":"HueLamp1","modelid":"LCT001","swversion":"65003148"},"2":{"state":{"on":true,"bri":254,"hue":33536,"sat":144,"xy":[0.3460,0.3568],"ct":201,"alert":"none","effect":"none","colormode":"hs","reachable":true},"type":"Extendedcolorlight","name":"HueLamp2","modelid":"LCT001","swversion":"65003148"}},"groups":{"1":{"action":{"on":true,"bri":254,"hue":33536,"sat":144,"xy":[0.3460,0.3568],"ct":201,"effect":"none","colormode":"xy"},"lights":["1","2"],"name":"Group1"}},"config":{"name":"Philipshue","mac":"00:00:88:00:bb:ee","dhcp":true,"ipaddress":"192.168.1.74","netmask":"255.255.255.0","gateway":"192.168.1.254","proxyaddress":"","proxyport":0,"UTC":"2012-10-29T12:00:00","whitelist":{"1028d66426293e821ecfd9ef1a0731df":{"lastusedate":"2012-10-29T12:00:00","createdate":"2012-10-29T12:00:00","name":"testuser"}},"swversion":"01003372","swupdate":{"updatestate":2,"checkforupdate":false,"devicetypes":{"bridge":true,"lights":["1","2","3"], "sensors":["4","5","6"]},"url":"","text":"010000000","notify":false},"swupdate2":{"checkforupdate":false,"lastchange":"2017-06-21T19:44:36","bridge":{"state":"noupdates","lastinstall":"2017-06-21T19:44:18"},"state":"noupdates","autoinstall":{"updatetime":"T14:00:00","on":false}},"schedules":{"1":{"name":"schedule","description":"","command":{"address":"/api/<username>/groups/0/action","body":{"on":true},"method":"PUT"},"time":"2012-10-29T12:00:00"}}}}`,
    59  		},
    60  		{
    61  			method: "POST",
    62  			path:   "",
    63  			data:   `[{"success":{"username": "83b7780291a6ceffbe0bd049104df", "clientkey": "33DDF493992908E3D97AAAA5A6F189E1"}}]`,
    64  		},
    65  		{
    66  			method: "DELETE",
    67  			path:   "/config/whitelist/ffffffffe0341b1b376a2389376a2389",
    68  			data:   `[{"success": "/config/whitelist/ffffffffe0341b1b376a2389376a2389 deleted."}]`,
    69  		},
    70  
    71  		// LIGHT
    72  		{
    73  			method: "GET",
    74  			path:   path.Join(username, "/lights"),
    75  			data:   `{"1":{"state":{"on":false,"bri":1,"hue":33761,"sat":254,"effect":"none","xy":[0.3171,0.3366],"ct":159,"alert":"none","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2017-10-15T12:07:34"},"type":"Extendedcolorlight","name":"Huecolorlamp7","modelid":"LCT001","manufacturername":"Philips","productname":"Huecolorlamp","capabilities":{"certified":true,"control":{"mindimlevel":5000,"maxlumen":600,"colorgamuttype":"B","colorgamut":[[0.675,0.322],[0.409,0.518],[0.167,0.04]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":false}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional"},"uniqueid":"00:17:88:01:00:bd:c7:b9-0b","swversion":"5.105.0.21169"},"2":{"state":{"on":false,"bri":1,"hue":35610,"sat":237,"effect":"none","xy":[0.1768,0.395],"ct":153,"alert":"none","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2017-10-18T12:50:40"},"type":"Extendedcolorlight","name":"Huelightstripplus1","modelid":"LST002","manufacturername":"Philips","productname":"Huelightstripplus","capabilities":{"certified":true,"control":{"mindimlevel":40,"maxlumen":1600,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.17,0.7],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"huelightstrip","function":"mixed","direction":"omnidirectional"},"uniqueid":"00:17:88:01:02:15:97:46-0b","swversion":"5.105.0.21169"}}`,
    76  		},
    77  		{
    78  			method: "GET",
    79  			path:   path.Join(username, "/lights/1"),
    80  			data:   `{"state":{"on":false,"bri":1,"hue":12594,"sat":251,"effect":"none","xy":[0.5474,0.4368],"alert":"none","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2017-12-13T01:59:13"},"type":"Colorlight","name":"Huebloom1","modelid":"LLC011","manufacturername":"Philips","productname":"Huebloom","capabilities":{"certified":true,"control":{"mindimlevel":10000,"maxlumen":120,"colorgamuttype":"A","colorgamut":[[0.704,0.296],[0.2151,0.7106],[0.138,0.08]]},"streaming":{"renderer":true,"proxy":false}},"config":{"archetype":"huebloom","function":"decorative","direction":"upwards"},"uniqueid":"00:17:88:01:00:c5:3b:e3-0b","swversion":"5.105.1.21778"}`,
    81  		},
    82  		{
    83  			method: "GET",
    84  			path:   path.Join(username, "/lights/new"),
    85  			data:   `{"7":{"name":"HueLamp7"},"8":{"name":"HueLamp8"},"lastscan":"2012-10-29T12:00:00"}`,
    86  		},
    87  		{
    88  			method: "POST",
    89  			path:   path.Join(username, "/lights"),
    90  			data:   `[{"success":{"/lights":"Searching for new devices"}}]`,
    91  		},
    92  		{
    93  			method: "PUT",
    94  			path:   path.Join(username, "/lights/1/state"),
    95  			data:   `[{"success":{"/lights/1/state/bri":200}},{"success":{"/lights/1/state/on":true}},{"success":{"/lights/1/state/hue":50000}}]`,
    96  		},
    97  		{
    98  			// Second route for identifying light testing
    99  			method: "PUT",
   100  			path:   path.Join(username, "/lights/2/state"),
   101  			data:   `[{"success":{"/lights/2/state/alert":"select"}}]`,
   102  		},
   103  		{
   104  			method: "PUT",
   105  			path:   path.Join(username, "/lights/1"),
   106  			data:   `[{"success":{"/lights/1/name":"Bedroom Light"}}]`,
   107  		},
   108  		{
   109  			method: "DELETE",
   110  			path:   path.Join(username, "/lights/1"),
   111  			data:   `[{"success":"/lights/<id> deleted"}]`,
   112  		},
   113  
   114  		// GROUP
   115  		{
   116  			method: "GET",
   117  			path:   "/groups",
   118  			data:   `{"1":{"name":"Group 1","lights":["1","2"],"type":"LightGroup","state":{"all_on":true,"any_on":true},"action":{"on":true,"bri":254,"hue":10000,"sat":254,"effect":"none","xy":[0.5,0.5],"ct":250,"alert":"select","colormode":"ct"}},"2":{"name":"Group 2","lights":["3","4","5"],"type":"LightGroup","state":{"all_on":true,"any_on":true},"action":{"on":true,"bri":153,"hue":4345,"sat":254,"effect":"none","xy":[0.5,0.5],"ct":250,"alert":"select","colormode":"ct"}},"3":{"name":"Group 3","lights":["1","2","3","4","5"],"sensors":[],"type":"Entertainment","state":{"all_on":true,"any_on":true},"recycle":false,"class":"TV","stream":{"proxymode":"auto","proxynode":"/lights/3","active":false,"owner":null},"locations":{"1":[0.93,-0.92,0.00],"2":[0.13,-0.85,1.00],"3":[-0.03,-0.86,1.00],"4":[-0.40,1.00,0.00],"5":[0.43,1.00,0.00]},"action":{"on":true,"bri":62,"hue":43749,"sat":189,"effect":"none","xy":[0.2133,0.2075],"ct":153,"alert":"select","colormode":"xy"}}}`,
   119  		},
   120  		{
   121  			method: "GET",
   122  			path:   "/groups/1",
   123  			data:   `{"action":{"on":true,"hue":0,"effect":"none","bri":100,"sat":100,"ct":500,"xy":[0.5,0.5]},"lights":["1","2"],"state":{"any_on":true,"all_on":true},"type":"Room","class":"Bedroom","name":"Masterbedroom"}`,
   124  		},
   125  		{
   126  			method: "PUT",
   127  			path:   "/groups/1",
   128  			data:   `[{"success":{"/groups/1/lights":["1"]}},{"success":{"/groups/1/name":"Bedroom"}}]`,
   129  		},
   130  		{
   131  			method: "PUT",
   132  			path:   "/groups/1/action",
   133  			data:   `[{"success":{"address":"/groups/1/action/on","value":true}},{"success":{"address":"/groups/1/action/effect","value":"colorloop"}},{"success":{"address":"/groups/1/action/hue","value":6000}}]`,
   134  		},
   135  		{
   136  			method: "POST",
   137  			path:   "/groups",
   138  			data:   `[{"success":{"id":"1"}}]`,
   139  		},
   140  		{
   141  			method: "DELETE",
   142  			path:   "/groups/1",
   143  			data:   `[{"success":"/groups/1 deleted."}]`,
   144  		},
   145  
   146  		// NON-ENTERTAINMENT GROUP
   147  		{
   148  			method: "GET",
   149  			path:   "/groups/2",
   150  			data:   `{"name":"Office","lights":["4","5","1","2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Office","action":{"on":true,"bri":92,"hue":53702,"sat":82,"effect":"none","xy":[    0.3693,    0.3006],"ct":233,"alert":"select","colormode":"xy"}}`,
   151  		},
   152  		{
   153  			method: "PUT",
   154  			path:   "/groups/2",
   155  			data:   `[{"error":{"type":6,"address":"/groups/2/stream","description":"parameter, /groups/2/stream, not available"}}]`,
   156  		},
   157  
   158  		// INACTIVE ENTERTAINMENT GROUP
   159  		{
   160  			method: "GET",
   161  			path:   "/groups/3",
   162  			data:   `{"name":"Group 3","lights":["1","2","3","4","5"],"sensors":[],"type":"Entertainment","state":{"all_on":true,"any_on":true},"recycle":false,"class":"TV","stream":{"proxymode":"auto","proxynode":"/lights/3","active":false,"owner":null},"locations":{"1":[0.93,-0.92,0.00],"2":[0.13,-0.85,1.00],"3":[-0.03,-0.86,1.00],"4":[-0.40,1.00,0.00],"5":[0.43,1.00,0.00]},"action":{"on":true,"bri":62,"hue":43749,"sat":189,"effect":"none","xy":[0.2133,0.2075],"ct":153,"alert":"select","colormode":"xy"}}`,
   163  		},
   164  		{
   165  			method: "PUT",
   166  			path:   "/groups/3",
   167  			data:   `[{"success":{"/groups/3/stream/active":true}}]`,
   168  		},
   169  
   170  		// ACTIVE ENTERTAINMENT GROUP
   171  		{
   172  			method: "GET",
   173  			path:   "/groups/4",
   174  			data:   `{"name":"Group 4","lights":["1","2","3","4","5"],"sensors":[],"type":"Entertainment","state":{"all_on":true,"any_on":true},"recycle":false,"class":"TV","stream":{"proxymode":"auto","proxynode":"/lights/3","active":true,"owner":"QZTPWY1ADZDM8IG188LBVOB5YV5O5OPZNCKTQPQB"},"locations":{"1":[0.93,-0.92,0.00],"2":[0.13,-0.85,1.00],"3":[-0.03,-0.86,1.00],"4":[-0.40,1.00,0.00],"5":[0.43,1.00,0.00]},"action":{"on":true,"bri":62,"hue":43749,"sat":189,"effect":"none","xy":[0.2133,0.2075],"ct":153,"alert":"select","colormode":"xy"}}`,
   175  		},
   176  		{
   177  			method: "PUT",
   178  			path:   "/groups/4",
   179  			data:   `[{"success":{"/groups/3/stream/active":false}}]`,
   180  		},
   181  
   182  		// ACTIVE ENTERTAINMENT GROUP FOR ENABLE ERROR
   183  		{
   184  			method: "GET",
   185  			path:   "/groups/5",
   186  			data:   `{"name":"Group 5","lights":["1","2","3","4","5"],"sensors":[],"type":"Entertainment","state":{"all_on":true,"any_on":true},"recycle":false,"class":"TV","stream":{"proxymode":"auto","proxynode":"/lights/3","active":true,"owner":"QZTPWY1ADZDM8IG188LBVOB5YV5O5OPZNCKTQPQB"},"locations":{"1":[0.93,-0.92,0.00],"2":[0.13,-0.85,1.00],"3":[-0.03,-0.86,1.00],"4":[-0.40,1.00,0.00],"5":[0.43,1.00,0.00]},"action":{"on":true,"bri":62,"hue":43749,"sat":189,"effect":"none","xy":[0.2133,0.2075],"ct":153,"alert":"select","colormode":"xy"}}`,
   187  		},
   188  		{
   189  			method: "PUT",
   190  			path:   "/groups/5",
   191  			data:   `[{"error":{"type":307,"address":"/groups/5/stream/active","description":"Cannot claim stream ownership"}}]`,
   192  		},
   193  
   194  		// ACTIVE ENTERTAINMENT GROUP FOR DISABLE ERROR
   195  		{
   196  			method: "GET",
   197  			path:   "/groups/6",
   198  			data:   `{"name":"Group 6","lights":["1","2","3","4","5"],"sensors":[],"type":"Entertainment","state":{"all_on":true,"any_on":true},"recycle":false,"class":"TV","stream":{"proxymode":"auto","proxynode":"/lights/3","active":true,"owner":"QZTPWY1ADZDM8IG188LBVOB5YV5O5OPZNCKTQPQB"},"locations":{"1":[0.93,-0.92,0.00],"2":[0.13,-0.85,1.00],"3":[-0.03,-0.86,1.00],"4":[-0.40,1.00,0.00],"5":[0.43,1.00,0.00]},"action":{"on":true,"bri":62,"hue":43749,"sat":189,"effect":"none","xy":[0.2133,0.2075],"ct":153,"alert":"select","colormode":"xy"}}`,
   199  		},
   200  		{
   201  			method: "PUT",
   202  			path:   "/groups/6",
   203  			data:   `[{"error":{"type":999,"address":"/groups/6/stream/active","description":"unspecified error"}}]`,
   204  		},
   205  
   206  		// SCENE
   207  		{
   208  			method: "GET",
   209  			path:   "/scenes",
   210  			data:   `{"4e1c6b20e-on-0":{"name":"Kathyon1449133269486","lights":["2","3"],"owner":"ffffffffe0341b1b376a2389376a2389","recycle":true,"locked":false,"appdata":{},"picture":"","lastupdated":"2015-12-03T08:57:13","version":1},"3T2SvsxvwteNNys":{"name":"Cozydinner","type":"GroupScene","group":"1","lights":["1","2"],"owner":"ffffffffe0341b1b376a2389376a2389","recycle":true,"locked":false,"appdata":{"version":1,"data":"myAppData"},"picture":"","lastupdated":"2015-12-03T10:09:22","version":2}}`,
   211  		},
   212  		{
   213  			method: "GET",
   214  			path:   "/scenes/4e1c6b20e-on-0",
   215  			data:   `{"name":"Cozy dinner","type":"GroupScene","group":"1","lights":["1"],"owner":"newdeveloper","recycle":true,"locked":false,"appdata":{},"picture":"","lastupdated":"2015-12-03T10:09:22","version":2,"lightstates":{"1":{"on":true,"bri":237,"xy":[0.5806,0.3903]}}}`,
   216  		},
   217  		{
   218  			method: "POST",
   219  			path:   "/scenes",
   220  			data:   `[{"success":{"address":"/scenes/ab341ef24/name","value":"Romanticdinner"}},{"success":{"address":"/scenes/ab3C41ef24/lights","value":["1","2"]}}]`,
   221  		},
   222  		{
   223  			method: "PUT",
   224  			path:   "/scenes/4e1c6b20e-on-0",
   225  			data:   `[{"success":{"/scenes/74bc26d5f-on-0/name":"Cozydinner"}},{"success":{"/scenes/74bc26d5f-on-0/storelightstate":true}},{"success":{"/scenes/74bc26d5f-on-0/lights":["2","3"]}}]`,
   226  		},
   227  		{
   228  			method: "PUT",
   229  			path:   "/scenes/4e1c6b20e-on-0/lightstates/1",
   230  			data:   `[{"success":{"address":"/scenes/ab341ef24/lights/1/state/on","value":true}},{"success":{"address":"/scenes/ab341ef24/lights/1/state/ct","value":200}}]`,
   231  		},
   232  		{
   233  			method: "DELETE",
   234  			path:   "/scenes/4e1c6b20e-on-0",
   235  			data:   `[{"success":"/scenes/3T2SvsxvwteNNys deleted"}]`,
   236  		},
   237  
   238  		// RULE
   239  		{
   240  			method: "GET",
   241  			path:   "/rules",
   242  			data:   `{ "1": { "name": "Wall Switch Rule", "lasttriggered": "2013-10-17T01:23:20", "creationtime": "2013-10-10T21:11:45", "timestriggered": 27, "owner": "78H56B12BA", "status": "enabled", "conditions": [ { "address": "/sensors/2/state/buttonevent", "operator": "eq", "value": "16" }, { "address": "/sensors/2/state/lastupdated", "operator": "dx" } ], "actions": [ { "address": "/groups/0/action", "method": "PUT", "body": { "scene": "S3" } } ] }, "2": { "name": "Wall Switch Rule 2" }} `,
   243  		},
   244  		{
   245  			method: "GET",
   246  			path:   "/rules/1",
   247  			data:   `{ "name": "Wall Switch Rule", "owner": "ruleOwner", "created": "2014-07-23T15:02:56", "lasttriggered": "none", "timestriggered": 0, "status": "enabled", "conditions": [ { "address": "/sensors/2/state/buttonevent", "operator": "eq", "value": "16" }, { "address": "/sensors/2/state/lastupdated", "operator": "dx" } ], "actions": [ { "address": "/groups/0/action", "method": "PUT", "body": { "scene": "S3" } } ] }`,
   248  		},
   249  		{
   250  			method: "POST",
   251  			path:   "/rules",
   252  			data:   `[{"success":{"id": "3"}}]`,
   253  		},
   254  		{
   255  			method: "PUT",
   256  			path:   "/rules/1",
   257  			data:   `[ { "success": { "/rules/1/actions": [ { "address": "/groups/0/action", "method": "PUT", "body": { "scene": "S3" } } ] } } ]`,
   258  		},
   259  		{
   260  			method: "DELETE",
   261  			path:   "/rules/1",
   262  			data:   `[{"success": "/rules/1 deleted."}]`,
   263  		},
   264  
   265  		// SCHEDULE
   266  		{
   267  			method: "GET",
   268  			path:   "/schedules",
   269  			data:   `{ "1": { "name": "Timer", "description": "", "command": { "address": "/api/s95jtYH8HUVWNkCO/groups/0/action", "body": { "scene": "02b12e930-off-0" }, "method": "PUT" }, "time": "PT00:01:00", "created": "2014-06-23T13:39:16", "status": "disabled", "autodelete": false, "starttime": "2014-06-23T13:39:16" }, "2": { "name": "Alarm", "description": "", "command": { "address": "/api/s95jtYH8HUVWNkCO/groups/0/action", "body": { "scene": "02b12e930-off-0" }, "method": "PUT" }, "localtime": "2014-06-23T19:52:00", "time": "2014-06-23T13:52:00", "created": "2014-06-23T13:38:57", "status": "disabled", "autodelete": false } }`,
   270  		},
   271  		{
   272  			method: "GET",
   273  			path:   "/schedules/1",
   274  			data:   `{ "name": "Wake up", "description": "My wake up alarm", "command": { "address": "/api/<username>/groups/1/action", "method": "PUT", "body": { "on": true } }, "time": "W124/T06:00:00" }`,
   275  		},
   276  		{
   277  			method: "POST",
   278  			path:   "/schedules",
   279  			data:   `[{"success":{"id": "2"}}]`,
   280  		},
   281  		{
   282  			method: "PUT",
   283  			path:   "/schedules/1",
   284  			data:   `[{ "success": {"/schedules/1/name": "Wake up"}}]`,
   285  		},
   286  		{
   287  			method: "DELETE",
   288  			path:   "/schedules/1",
   289  			data:   `[{"success": "/schedules/1 deleted."}]`,
   290  		},
   291  
   292  		// SENSOR
   293  		{
   294  			method: "GET",
   295  			path:   "/sensors",
   296  			data:   `{ "1": { "state": { "daylight": false, "lastupdated": "2014-06-27T07:38:51" }, "config": { "on": true, "long": "none", "lat": "none", "sunriseoffset": 50, "sunsetoffset": 50 }, "name": "Daylight", "type": "Daylight", "modelid": "PHDL00", "manufacturername": "Philips", "swversion": "1.0" }, "2": { "state": { "buttonevent": 0, "lastupdated": "none" }, "config": { "on": true }, "name": "Tap Switch 2", "type": "ZGPSwitch", "modelid": "ZGPSWITCH", "manufacturername": "Philips", "uniqueid": "00:00:00:00:00:40:03:50-f2" } }`,
   297  		},
   298  		{
   299  			method: "GET",
   300  			path:   "/sensors/1",
   301  			data:   `{ "state":{ "buttonevent": 34, "lastupdated":"2013-03-25T13:32:34" }, "name": "Wall tap 1", "modelid":"ZGPSWITCH", "uniqueid":"01:23:45:67:89:AB-12", "manufacturername": "Philips", "swversion":"1.0", "type":  "ZGPSwitch" }`,
   302  		},
   303  		{
   304  			method: "POST",
   305  			path:   "/sensors",
   306  			data:   `[ { "success": { "/sensors": "Searching for new devices"}}]`,
   307  		},
   308  		{
   309  			method: "POST",
   310  			path:   "/sensors",
   311  			data:   `[{"success":{"id": "4"}}]`,
   312  		},
   313  		{
   314  			method: "GET",
   315  			path:   "/sensors/new",
   316  			data:   `{ "7": {"name": "Hue Tap 1"}, "8": {"name": "Button 3"}, "lastscan":"2013-05-22T10:24:00" }`,
   317  		},
   318  		{
   319  			method: "PUT",
   320  			path:   "/sensors/1",
   321  			data:   `[{"success":{"/sensors/2/name":"Bedroom Tap"}}]`,
   322  		},
   323  		{
   324  			method: "DELETE",
   325  			path:   "/sensors/1",
   326  			data:   `[{"success": "/sensors/1 deleted."}]`,
   327  		},
   328  		{
   329  			method: "PUT",
   330  			path:   "/sensors/1/config",
   331  			data:   `[{"success":{"/sensors/2/config/on":true}}]`,
   332  		},
   333  		{
   334  			method: "PUT",
   335  			path:   "/sensors/1/state",
   336  			data:   `[{"success":{"/sensors/1/state/presence": false}}]`,
   337  		},
   338  
   339  		// CAPABILITIES
   340  		{
   341  			method: "GET",
   342  			path:   "/capabilities",
   343  			data:   `{ "lights":{ "available": 10 }, "sensors":{ "available": 60, "clip": { "available": 60 }, "zll": { "available": 60 }, "zgp": { "available": 60 } }, "groups": {}, "scenes": { "available": 100, "lightstates": { "available": 1500 } }, "rules": {}, "schedules": {}, "resourcelinks": {}, "whitelists": {}, "timezones": { "values":[ "Africa/Abidjan", "Africa/Accra", "Pacific/Wallis", "US/Pacific-New" ] } }`,
   344  		},
   345  
   346  		// RESOURCELINK
   347  		{
   348  			method: "GET",
   349  			path:   "/resourcelinks",
   350  			data:   `{ "1": { "name": "Sunrise", "description": "Carla's wakeup experience", "class": 1, "owner": "78H56B12BAABCDEF", "links": ["/schedules/2", "/schedules/3", "/scenes/ABCD", "/scenes/EFGH", "/groups/8"] }, "2": { "name": "Sunrise 2" } }`,
   351  		},
   352  		{
   353  			method: "GET",
   354  			path:   "/resourcelinks/1",
   355  			data:   `{ "name": "Sunrise", "description": "Carla's wakeup experience", "type":"Link", "class": 1, "owner": "78H56B12BAABCDEF", "links": ["/schedules/2", "/schedules/3", "/scenes/ABCD", "/scences/EFGH", "/groups/8"] }`,
   356  		},
   357  		{
   358  			method: "POST",
   359  			path:   "/resourcelinks",
   360  			data:   `[{"success":{"id": "3"}}]`,
   361  		},
   362  		{
   363  			method: "PUT",
   364  			path:   "/resourcelinks/1",
   365  			data:   `[{ "success": { "/resourcelinks/1/name": "Sunrise" } }, { "success": { "/resourcelinks/1/description": "Carla's wakeup experience" } }]`,
   366  		},
   367  		{
   368  			method: "DELETE",
   369  			path:   "/resourcelinks/1",
   370  			data:   `[{"success": "/resourcelinks/1 deleted."}]`,
   371  		},
   372  	}
   373  
   374  	httpmock.Activate()
   375  
   376  	for _, test := range tests {
   377  		if test.url == "" {
   378  			test.url = fmt.Sprintf("http://%s/api%s", hostname, test.path)
   379  		}
   380  		httpmock.RegisterResponder(test.method, test.url, httpmock.NewStringResponder(200, test.data))
   381  	}
   382  
   383  	// Register a responder for bad requests
   384  	paths := []string{
   385  		"",
   386  		"config",
   387  		"/config",
   388  		"/config",
   389  		"/config/whitelist/ffffffffe0341b1b376a2389376a2389",
   390  		path.Join("/invalid_password", "/lights"),
   391  		path.Join(username, "/lights"),
   392  		path.Join(username, "/lights/1"),
   393  		path.Join(username, "/lights/new"),
   394  		path.Join(username, "/lights"),
   395  		path.Join(username, "/lights/1/state"),
   396  		path.Join(username, "/lights/1"),
   397  		path.Join(username, "/lights/1"),
   398  		"/groups",
   399  		"/groups/1",
   400  		"/groups/1",
   401  		"/groups/1/action",
   402  		"/groups",
   403  		"/groups/1",
   404  		"/scenes",
   405  		"/scenes/4e1c6b20e-on-0",
   406  		"/scenes",
   407  		"/scenes/4e1c6b20e-on-0",
   408  		"/scenes/4e1c6b20e-on-0/lightstates/1",
   409  		"/scenes/4e1c6b20e-on-0",
   410  		"/rules",
   411  		"/rules/1",
   412  		"/rules",
   413  		"/rules/1",
   414  		"/rules/1",
   415  		"/schedules",
   416  		"/schedules/1",
   417  		"/schedules",
   418  		"/schedules/1",
   419  		"/schedules/1",
   420  		"/sensors",
   421  		"/sensors/1",
   422  		"/sensors",
   423  		"/sensors",
   424  		"/sensors/new",
   425  		"/sensors/1",
   426  		"/sensors/1",
   427  		"/sensors/1/config",
   428  		"/sensors/1/state",
   429  		"/capabilities",
   430  		"/resourcelinks",
   431  		"/resourcelinks/1",
   432  		"/resourcelinks",
   433  		"/resourcelinks/1",
   434  		"/resourcelinks/1",
   435  	}
   436  
   437  	// Register responder for errors
   438  	for _, p := range paths {
   439  		response := []byte("not json")
   440  		httpmock.RegisterResponder("GET", fmt.Sprintf("http://%s/api%s", badHostname, p), httpmock.NewBytesResponder(200, response))
   441  		httpmock.RegisterResponder("POST", fmt.Sprintf("http://%s/api%s", badHostname, p), httpmock.NewBytesResponder(200, response))
   442  		httpmock.RegisterResponder("PUT", fmt.Sprintf("http://%s/api%s", badHostname, p), httpmock.NewBytesResponder(200, response))
   443  		httpmock.RegisterResponder("DELETE", fmt.Sprintf("http://%s/api%s", badHostname, p), httpmock.NewBytesResponder(200, response))
   444  	}
   445  
   446  }
   447  
   448  func TestDiscoverAndLogin(t *testing.T) {
   449  	bridge, err := Discover()
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  	bridge = bridge.Login(username)
   454  	t.Logf("Successfully logged in to bridge")
   455  }
   456  
   457  func TestDiscoverAllBridges(t *testing.T) {
   458  	bridges, err := DiscoverAll()
   459  	if err != nil {
   460  		t.Fatal(err)
   461  	}
   462  	t.Logf("Discovered %d bridge(s)", len(bridges))
   463  	for i, bridge := range bridges {
   464  		t.Logf("%d: ", i)
   465  		t.Logf("  Host: %s", bridge.Host)
   466  		t.Logf("  User: %s", bridge.User)
   467  		t.Logf("  ID: %s", bridge.ID)
   468  	}
   469  }
   470  
   471  func Test_unmarshalError(t *testing.T) {
   472  	s := struct {
   473  		Name string `json:"name"`
   474  	}{
   475  		Name: "amimof",
   476  	}
   477  	err := unmarshal([]byte(`not json`), s)
   478  	assert.NotNil(t, err)
   479  }