Skip to content

Commit

Permalink
chore: refactor code and add missing tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mdmfernandes committed Oct 6, 2024
1 parent c586006 commit 112e517
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 190 deletions.
38 changes: 38 additions & 0 deletions common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package foscam

import (
"io"
"net/http"
)

// Send a GET request to URL via the provided client.
func getRequest(client HTTPClient, url string) ([]byte, error) {
res, err := client.Get(url)
if err != nil {
return nil, &CameraError{err.Error()}
}
defer res.Body.Close()

if res.StatusCode != 200 {
return nil, &BadStatusError{URL: url, Status: res.StatusCode, Expected: http.StatusOK}
}

b, _ := io.ReadAll(res.Body)

return b, nil
}

// Get a snapshot from the URL, via the provided client.
func getSnap(client HTTPClient, url string) ([]byte, error) {
b, err := getRequest(client, url)
if err != nil {
return nil, err
}

// Check that camera returns a JPEG image
if err = isJpeg(b); err != nil {
return nil, err
}

return b, nil
}
81 changes: 81 additions & 0 deletions common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package foscam

import (
"bytes"
"errors"
"io"
"net/http"
"testing"

"github.com/mdmfernandes/go-foscam/mocks"
"github.com/mdmfernandes/go-foscam/testutil"
)

func Test_getRequest(t *testing.T) {
// request() arguments
type args struct {
client *mocks.MockHTTPClient
url string
}

tests := []struct {
name string
args *args
want []byte
wantErr error
wantGetCalled int
}{
{
name: "Camera error",
args: &args{
client: &mocks.MockHTTPClient{
GetFunc: func(url string) (*http.Response, error) {
return nil, errors.New("some error")
},
},
url: "",
},
want: nil,
wantErr: &CameraError{"some error"},
wantGetCalled: 1,
},
{
name: "Unexpected status code (404)",
args: &args{
client: &mocks.MockHTTPClient{
GetFunc: func(url string) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusNotFound,
Body: io.NopCloser(bytes.NewBufferString("don't care")),
}, nil
},
},
url: "",
},
want: nil,
wantErr: &BadStatusError{"", http.StatusNotFound, http.StatusOK},
wantGetCalled: 1,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := getRequest(tt.args.client, tt.args.url)

// Check how many times the client was called
if tt.wantGetCalled != tt.args.client.GetCount {
t.Errorf("Number of requests: got = %d; want = %d", tt.args.client.GetCount, tt.wantGetCalled)
}

// Check for errors
if !testutil.EqualError(t, err, tt.wantErr) {
t.Errorf("Error: got = %v; want = %v", err, tt.wantErr)
}

// Check for the response body
if !bytes.Equal(b, tt.want) {
t.Errorf("Response: got = %v; want = %v", b, tt.want)
}
})
}
}
9 changes: 0 additions & 9 deletions convert.go

This file was deleted.

32 changes: 0 additions & 32 deletions convert_test.go

This file was deleted.

10 changes: 10 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,13 @@ type BadResponseError struct {
func (b *BadResponseError) Error() string {
return fmt.Sprintf("Unexpected response. Want: %#v, Got: %#v", b.Want, b.Got)
}

// BadResponseError represents a bad response from the camera.
type InvalidMIMETypeError struct {
Want any
Got any
}

func (b *InvalidMIMETypeError) Error() string {
return fmt.Sprintf("Invalid MIME type. Want: %#v, Got: %#v", b.Want, b.Got)
}
37 changes: 4 additions & 33 deletions fi8919w.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package foscam

import (
"fmt"
"io"
"net/http"
"strings"

"github.com/google/go-querystring/query"
)
Expand All @@ -29,17 +26,11 @@ func (c *fi8919w) ChangeMotionStatus(enable bool) error {
q.Encode(),
b2u(enable))

res, err := c.Client.Get(url)
b, err := getRequest(c.Client, url)
if err != nil {
return &CameraError{err.Error()}
return err
}
defer res.Body.Close()

if res.StatusCode != 200 {
return &BadStatusError{URL: c.URL, Status: res.StatusCode, Expected: http.StatusOK}
}

b, _ := io.ReadAll(res.Body)
got := string(b)
want := "ok.\n"
if got != want {
Expand All @@ -49,30 +40,10 @@ func (c *fi8919w) ChangeMotionStatus(enable bool) error {
return nil
}

// SnapPicture takes a snapshot and returns the picture in a byte slice.
func (c *fi8919w) SnapPicture() ([]byte, error) {
q, _ := query.Values(c)
url := fmt.Sprintf("%s/snapshot.cgi?%s", c.URL, q.Encode())

res, err := c.Client.Get(url)
if err != nil {
return nil, &CameraError{err.Error()}
}
defer res.Body.Close()

if res.StatusCode != 200 {
return nil, &BadStatusError{URL: c.URL, Status: res.StatusCode, Expected: http.StatusOK}
}

b, _ := io.ReadAll(res.Body)

// Check that camera returns a JPEG image
if mime := http.DetectContentType(b); mime != jpegMime {
// If camera returns plain text, show it in the error message
if strings.Contains(mime, "text/plain") {
mime = string(b)
}
return nil, &BadResponseError{Want: jpegMime, Got: mime}
}

return b, nil
return getSnap(c.Client, url)
}
23 changes: 0 additions & 23 deletions fi8919w_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,6 @@ func TestFI8919w_ChangeMotionStatus(t *testing.T) {
wantErr: nil,
wantGetCalled: 1,
},
{
name: "Camera error",
Client: &mocks.MockHTTPClient{
GetFunc: func(url string) (*http.Response, error) {
return nil, http.ErrAbortHandler
},
},
wantErr: &CameraError{http.ErrAbortHandler.Error()},
wantGetCalled: 1,
},
{
name: "Get status code unexpected",
Client: &mocks.MockHTTPClient{
GetFunc: func(url string) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusNotFound,
Body: io.NopCloser(strings.NewReader("ok.\n")),
}, nil
},
},
wantErr: &BadStatusError{Status: http.StatusNotFound, Expected: http.StatusOK},
wantGetCalled: 1,
},
{
name: "Get unexpected response",
Client: &mocks.MockHTTPClient{
Expand Down
49 changes: 5 additions & 44 deletions fi9800p.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,10 @@ package foscam
import (
"encoding/xml"
"fmt"
"io"
"net/http"
"strings"

"github.com/google/go-querystring/query"
)

const jpegMime string = "image/jpeg"

// fi9800p is a camera Foscam FI9800P.
// We don't need to export this struct since we are using an interface factory.
// see the file foscam.go for more details
Expand Down Expand Up @@ -64,18 +59,11 @@ func (c *fi9800p) updateMotionDetect(mc fi9800pMotion) error {
qm, _ := query.Values(mc) // Motion Config
url := fmt.Sprintf("%s/cgi-bin/CGIProxy.fcgi?cmd=setMotionDetectConfig&%s&%s", c.URL, qm.Encode(), qc.Encode())

res, err := c.Client.Get(url)
b, err := getRequest(c.Client, url)
if err != nil {
return &CameraError{err.Error()}
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return &BadStatusError{URL: c.URL, Status: res.StatusCode, Expected: http.StatusOK}
return err
}

b, _ := io.ReadAll(res.Body)

var mr fi9800pResponse

if err := xml.Unmarshal(b, &mr); err != nil {
Expand All @@ -97,17 +85,11 @@ func (c *fi9800p) GetMotionDetect() (fi9800pMotion, error) {
q, _ := query.Values(c)
url := fmt.Sprintf("%s/cgi-bin/CGIProxy.fcgi?cmd=getMotionDetectConfig&%s", c.URL, q.Encode())

res, err := c.Client.Get(url)
b, err := getRequest(c.Client, url)
if err != nil {
return mc, &CameraError{err.Error()}
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return mc, &BadStatusError{URL: c.URL, Status: res.StatusCode, Expected: http.StatusOK}
return mc, err
}

b, _ := io.ReadAll(res.Body)
if err = xml.Unmarshal(b, &mc); err != nil {
return mc, err
}
Expand Down Expand Up @@ -137,26 +119,5 @@ func (c *fi9800p) SnapPicture() ([]byte, error) {
q, _ := query.Values(c)
url := fmt.Sprintf("%s/cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&%s", c.URL, q.Encode())

res, err := c.Client.Get(url)
if err != nil {
return nil, &CameraError{err.Error()}
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return nil, &BadStatusError{URL: c.URL, Status: res.StatusCode, Expected: http.StatusOK}
}

b, _ := io.ReadAll(res.Body)

// Check that camera returns a JPEG image
if mime := http.DetectContentType(b); mime != jpegMime {
// If camera returns plain text, show it in the error message
if strings.Contains(mime, "text/plain") {
mime = string(b)
}
return nil, &BadResponseError{Want: jpegMime, Got: mime}
}

return b, nil
return getSnap(c.Client, url)
}
Loading

0 comments on commit 112e517

Please sign in to comment.