package particle import ( "errors" "net/http" "net/http/httptest" "net/url" "strings" "testing" "github.com/donovanhide/eventsource" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gobot.io/x/gobot/v2" ) // HELPERS func createTestServer(handler func(w http.ResponseWriter, r *http.Request)) *httptest.Server { return httptest.NewServer(http.HandlerFunc(handler)) } func getDummyResponseForPath(t *testing.T, path string, dummyResponse string) *httptest.Server { t.Helper() dummyData := []byte(dummyResponse) return createTestServer(func(w http.ResponseWriter, r *http.Request) { actualPath := "/v1/devices" + path if r.URL.Path != actualPath { //nolint:testifylint // TODO: fix later require.Fail(t, "Path doesn't match, expected %#v, got %#v", actualPath, r.URL.Path) } _, _ = w.Write(dummyData) }) } func getDummyResponseForPathWithParams( t *testing.T, path string, params []string, dummyResponse string, ) *httptest.Server { t.Helper() dummyData := []byte(dummyResponse) return createTestServer(func(w http.ResponseWriter, r *http.Request) { actualPath := "/v1/devices" + path if r.URL.Path != actualPath { //nolint:testifylint // TODO: fix later require.Fail(t, "Path doesn't match, expected %#v, got %#v", actualPath, r.URL.Path) } _ = r.ParseForm() for key, value := range params { if r.Form["params"][key] != value { t.Error("Expected param to be " + r.Form["params"][key] + " but was " + value) } } _, _ = w.Write(dummyData) }) } func initTestAdaptor() *Adaptor { return NewAdaptor("myDevice", "token") } func initTestAdaptorWithServo() *Adaptor { a := NewAdaptor("myDevice", "token") a.servoPins["1"] = true return a } // TESTS func TestAdaptor(t *testing.T) { var a interface{} = initTestAdaptor() _, ok := a.(gobot.Adaptor) if !ok { require.Fail(t, "Adaptor{} should be a gobot.Adaptor") } } func TestNewAdaptor(t *testing.T) { // does it return a pointer to an instance of Adaptor? var a interface{} = initTestAdaptor() core, ok := a.(*Adaptor) if !ok { require.Fail(t, "NewAdaptor() should have returned a *Adaptor") } assert.Equal(t, "https://api.particle.io", core.APIServer) assert.True(t, strings.HasPrefix(core.Name(), "Particle")) core.SetName("sparkie") assert.Equal(t, "sparkie", core.Name()) } func TestAdaptorConnect(t *testing.T) { a := initTestAdaptor() require.NoError(t, a.Connect()) } func TestAdaptorFinalize(t *testing.T) { a := initTestAdaptor() _ = a.Connect() require.NoError(t, a.Finalize()) } func TestAdaptorAnalogRead(t *testing.T) { // When no error response := `{"return_value": 5.2}` params := []string{"A1"} a := initTestAdaptor() testServer := getDummyResponseForPathWithParams(t, "/"+a.DeviceID+"/analogread", params, response) a.setAPIServer(testServer.URL) defer testServer.Close() val, _ := a.AnalogRead("A1") assert.Equal(t, 5, val) } func TestAdaptorAnalogReadError(t *testing.T) { a := initTestAdaptor() // When error testServer := createTestServer(func(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) }) defer testServer.Close() a.setAPIServer(testServer.URL) val, _ := a.AnalogRead("A1") assert.Equal(t, 0, val) } func TestAdaptorPwmWrite(t *testing.T) { response := `{}` params := []string{"A1,1"} a := initTestAdaptor() testServer := getDummyResponseForPathWithParams(t, "/"+a.DeviceID+"/analogwrite", params, response) defer testServer.Close() a.setAPIServer(testServer.URL) _ = a.PwmWrite("A1", 1) } func TestAdaptorAnalogWrite(t *testing.T) { response := `{}` params := []string{"A1,1"} a := initTestAdaptor() testServer := getDummyResponseForPathWithParams(t, "/"+a.DeviceID+"/analogwrite", params, response) defer testServer.Close() a.setAPIServer(testServer.URL) _ = a.AnalogWrite("A1", 1) } func TestAdaptorDigitalWrite(t *testing.T) { // When HIGH response := `{}` params := []string{"D7,HIGH"} a := initTestAdaptor() testServer := getDummyResponseForPathWithParams(t, "/"+a.DeviceID+"/digitalwrite", params, response) a.setAPIServer(testServer.URL) _ = a.DigitalWrite("D7", 1) testServer.Close() // When LOW params = []string{"D7,LOW"} testServer = getDummyResponseForPathWithParams(t, "/"+a.DeviceID+"/digitalwrite", params, response) defer testServer.Close() a.setAPIServer(testServer.URL) _ = a.DigitalWrite("D7", 0) } func TestAdaptorServoOpen(t *testing.T) { response := `{}` params := []string{"1"} a := initTestAdaptor() testServer := getDummyResponseForPathWithParams(t, "/"+a.DeviceID+"/servoOpen", params, response) defer testServer.Close() a.setAPIServer(testServer.URL) _ = a.servoPinOpen("1") } func TestAdaptorServoWrite(t *testing.T) { response := `{}` params := []string{"1,128"} a := initTestAdaptorWithServo() testServer := getDummyResponseForPathWithParams(t, "/"+a.DeviceID+"/servoSet", params, response) defer testServer.Close() a.setAPIServer(testServer.URL) _ = a.ServoWrite("1", 128) } func TestAdaptorDigitalRead(t *testing.T) { // When HIGH response := `{"return_value": 1}` params := []string{"D7"} a := initTestAdaptor() testServer := getDummyResponseForPathWithParams(t, "/"+a.DeviceID+"/digitalread", params, response) a.setAPIServer(testServer.URL) val, _ := a.DigitalRead("D7") assert.Equal(t, 1, val) testServer.Close() // When LOW response = `{"return_value": 0}` testServer = getDummyResponseForPathWithParams(t, "/"+a.DeviceID+"/digitalread", params, response) a.setAPIServer(testServer.URL) defer testServer.Close() val, _ = a.DigitalRead("D7") assert.Equal(t, 0, val) } func TestAdaptorDigitalReadError(t *testing.T) { a := initTestAdaptor() // When error testServer := createTestServer(func(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) }) defer testServer.Close() a.setAPIServer(testServer.URL) val, _ := a.DigitalRead("D7") assert.Equal(t, -1, val) } func TestAdaptorFunction(t *testing.T) { response := `{"return_value": 1}` a := initTestAdaptor() testServer := getDummyResponseForPath(t, "/"+a.DeviceID+"/hello", response) a.setAPIServer(testServer.URL) val, _ := a.Function("hello", "100,200") assert.Equal(t, 1, val) testServer.Close() // When not existent response = `{"ok": false, "error": "timeout"}` testServer = getDummyResponseForPath(t, "/"+a.DeviceID+"/hello", response) a.setAPIServer(testServer.URL) _, err := a.Function("hello", "") require.ErrorContains(t, err, "timeout") testServer.Close() } func TestAdaptorVariable(t *testing.T) { // When String response := `{"result": "1"}` a := initTestAdaptor() testServer := getDummyResponseForPath(t, "/"+a.DeviceID+"/variable_name", response) a.setAPIServer(testServer.URL) val, _ := a.Variable("variable_name") assert.Equal(t, "1", val) testServer.Close() // When float response = `{"result": 1.1}` testServer = getDummyResponseForPath(t, "/"+a.DeviceID+"/variable_name", response) a.setAPIServer(testServer.URL) val, _ = a.Variable("variable_name") assert.Equal(t, "1.1", val) testServer.Close() // When int response = `{"result": 1}` testServer = getDummyResponseForPath(t, "/"+a.DeviceID+"/variable_name", response) a.setAPIServer(testServer.URL) val, _ = a.Variable("variable_name") assert.Equal(t, "1", val) testServer.Close() // When bool response = `{"result": true}` testServer = getDummyResponseForPath(t, "/"+a.DeviceID+"/variable_name", response) a.setAPIServer(testServer.URL) val, _ = a.Variable("variable_name") assert.Equal(t, "true", val) testServer.Close() // When not existent response = `{"ok": false, "error": "Variable not found"}` testServer = getDummyResponseForPath(t, "/"+a.DeviceID+"/not_existent", response) a.setAPIServer(testServer.URL) _, err := a.Variable("not_existent") require.ErrorContains(t, err, "Variable not found") testServer.Close() } func TestAdaptorSetAPIServer(t *testing.T) { a := initTestAdaptor() apiServer := "new_api_server" assert.NotEqual(t, apiServer, a.APIServer) a.setAPIServer(apiServer) assert.Equal(t, apiServer, a.APIServer) } func TestAdaptorDeviceURL(t *testing.T) { // When APIServer is set a := initTestAdaptor() a.setAPIServer("http://server") a.DeviceID = "devID" assert.Equal(t, "http://server/v1/devices/devID", a.deviceURL()) // When APIServer is not set a = &Adaptor{name: "particleie", DeviceID: "myDevice", AccessToken: "token"} assert.Equal(t, "https://api.particle.io/v1/devices/myDevice", a.deviceURL()) } func TestAdaptorPinLevel(t *testing.T) { a := initTestAdaptor() assert.Equal(t, "HIGH", a.pinLevel(1)) assert.Equal(t, "LOW", a.pinLevel(0)) assert.Equal(t, "LOW", a.pinLevel(5)) } func TestAdaptorPostToparticle(t *testing.T) { a := initTestAdaptor() // When error on request vals := url.Values{} vals.Add("error", "error") resp, err := a.request("POST", "http://invalid%20host.com", vals) if err == nil { t.Error("request() should return an error when request was unsuccessful but returned", resp) } // When error reading body // Pending // When response.Status is not 200 testServer := createTestServer(func(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) }) defer testServer.Close() resp, err = a.request("POST", testServer.URL+"/existent", vals) if err == nil { t.Error("request() should return an error when status is not 200 but returned", resp) } } func TestAdaptorEventStream(t *testing.T) { a := initTestAdaptor() var url string eventSource = func(u string) (chan eventsource.Event, chan error, error) { url = u return nil, nil, nil } _, _ = a.EventStream("all", "ping") assert.Equal(t, "https://api.particle.io/v1/events/ping?access_token=token", url) _, _ = a.EventStream("devices", "ping") assert.Equal(t, "https://api.particle.io/v1/devices/events/ping?access_token=token", url) _, _ = a.EventStream("device", "ping") assert.Equal(t, "https://api.particle.io/v1/devices/myDevice/events/ping?access_token=token", url) _, err := a.EventStream("nothing", "ping") require.ErrorContains(t, err, "source param should be: all, devices or device") eventSource = func(u string) (chan eventsource.Event, chan error, error) { return nil, nil, errors.New("error connecting sse") } _, err = a.EventStream("devices", "") require.ErrorContains(t, err, "error connecting sse") eventChan := make(chan eventsource.Event) errorChan := make(chan error) eventSource = func(u string) (chan eventsource.Event, chan error, error) { return eventChan, errorChan, nil } _, err = a.EventStream("devices", "") require.NoError(t, err) }