Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resources #9

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.envrc
cover.out
ngrok.yml
cover.out

*.mp3
*.ogg
*.opus
*.wav
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,31 @@

Package alice provides helpers for developing skills for Alice virtual assistant
via Yandex.Dialogs platform.

# Example

```go
responder := func(ctx context.Context, request *alice.Request) (*alice.ResponsePayload, error) {
return &alice.ResponsePayload{
Text: "Bye!",
EndSession: true,
}, nil
}

h := alice.NewHandler(responder)
h.Errorf = log.Printf
http.Handle("/", h)

const addr = "127.0.0.1:8080"
log.Printf("Listening on http://%s ...", addr)
log.Fatal(http.ListenAndServe(addr, nil))
```

See [documentation](https://godoc.org/github.com/AlekSi/alice) and [examples](examples).

# License

Copyright (c) 2019 Alexey Palazhchenko. [MIT-style license](LICENSE).

Tests use the following resources:
* https://freesound.org/people/prucanada/sounds/415341/ by prucanada.
5 changes: 3 additions & 2 deletions doc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import (
)

func Example() {
h := alice.NewHandler(func(ctx context.Context, request *alice.Request) (*alice.ResponsePayload, error) {
responder := func(ctx context.Context, request *alice.Request) (*alice.ResponsePayload, error) {
return &alice.ResponsePayload{
Text: "Bye!",
EndSession: true,
}, nil
})
}

h := alice.NewHandler(responder)
h.Errorf = log.Printf
http.Handle("/", h)

Expand Down
2 changes: 2 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return
}
req.Body = ioutil.NopCloser(&body)
req.ContentLength = int64(body.Len())
req.TransferEncoding = nil
}

b, err := httputil.DumpRequest(req, true)
Expand Down
204 changes: 204 additions & 0 deletions resources/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package resources

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ST1000: at least one file in a package should have a package comment (from stylecheck)


import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/httputil"
"os"
"path/filepath"
"time"

"github.com/AlekSi/alice"
)

type Quota struct {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported type Quota should have comment or be unexported (from golint)

Total int
Used int
}

type Sound struct {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported type Sound should have comment or be unexported (from golint)

ID string
SkillID string
Size *int
OriginalName string
CreatedAt time.Time
IsProcessed bool
Error *string
}

type Client struct {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported type Client should have comment or be unexported (from golint)

SkillID string
OAuthToken string
HTTPClient *http.Client

// debugging options
Debugf alice.Printf // debug logger
Indent bool // indent requests and responses
StrictDecoder bool // disallow unexpected fields in responses
}

func (c *Client) do(req *http.Request, respBody interface{}) error {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cognitive complexity 32 of func (*Client).do is high (> 30) (from gocognit)

httpClient := c.HTTPClient
if httpClient == nil {
httpClient = http.DefaultClient
}

var jsonRequst bool
if c.OAuthToken != "" {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if statements should only be cuddled with assignments used in the if statement itself (from wsl)

req.Header.Set("Authorization", "OAuth "+c.OAuthToken)
}
if req.Body != nil && req.Header.Get("Content-Type") == "" {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if statements should only be cuddled with assignments (from wsl)

jsonRequst = true
req.Header.Set("Content-Type", "application/json; charset=utf-8")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only cuddled expressions if assigning variable or using from line above (from wsl)

}

if c.Debugf != nil {
if c.Indent && jsonRequst {
b, err := ioutil.ReadAll(req.Body)
if err != nil {
return err
}

var body bytes.Buffer
if err = json.Indent(&body, b, "", " "); err != nil {
return err
}
req.Body = ioutil.NopCloser(&body)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assignments should only be cuddled with other assignments (from wsl)

req.ContentLength = int64(body.Len())
req.TransferEncoding = nil
}

b, err := httputil.DumpRequestOut(req, jsonRequst)
if err != nil {
return err
}
c.debugf("Request:\n%s", b)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expressions should not be cuddled with blocks (from wsl)

}

resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close() //nolint:errcheck

if c.Debugf != nil {
if c.Indent {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}

var body bytes.Buffer
if err = json.Indent(&body, b, "", " "); err != nil {
return err
}
resp.Body = ioutil.NopCloser(&body)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assignments should only be cuddled with other assignments (from wsl)

resp.ContentLength = int64(body.Len())
resp.TransferEncoding = nil
}

b, err := httputil.DumpResponse(resp, true)
if err != nil {
return err
}
c.debugf("Response:\n%s", b)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expressions should not be cuddled with blocks (from wsl)

}

if resp.StatusCode/100 != 2 {
return fmt.Errorf("status code %d", resp.StatusCode)
}

d := json.NewDecoder(resp.Body)
if c.StrictDecoder {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if statements should only be cuddled with assignments used in the if statement itself (from wsl)

d.DisallowUnknownFields()
}
return d.Decode(&respBody)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return statements should not be cuddled if block has more than two lines (from wsl)

}

func (c *Client) debugf(format string, a ...interface{}) {
if c.Debugf != nil {
c.Debugf(format, a...)
}
}

type StatusResponse struct {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported type StatusResponse should have comment or be unexported (from golint)

Images struct {
Quota Quota
}
Sounds struct {
Quota Quota
}
}

func (c *Client) Status() (*StatusResponse, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported method Client.Status should have comment or be unexported (from golint)

req, err := http.NewRequest("GET", "https://dialogs.yandex.net/api/v1/status", nil)
if err != nil {
return nil, err
}

var res StatusResponse
if err = c.do(req, &res); err != nil {
return nil, err
}
return &res, nil

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return statements should not be cuddled if block has more than two lines (from wsl)

}

func (c *Client) UploadSound(name string, r io.Reader) (*Sound, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported method Client.UploadSound should have comment or be unexported (from golint)

var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
fw, err := mw.CreateFormFile("file", name)
if err != nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only one cuddle assignment allowed before if statement (from wsl)

return nil, err
}
if _, err = io.Copy(fw, r); err != nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if statements should only be cuddled with assignments (from wsl)

return nil, err
}
if err = mw.Close(); err != nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if statements should only be cuddled with assignments (from wsl)

return nil, err
}

req, err := http.NewRequest("POST", "https://dialogs.yandex.net/api/v1/skills/"+c.SkillID+"/sounds", &buf)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", mw.FormDataContentType())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expressions should not be cuddled with blocks (from wsl)


var res struct {
Sound Sound
}
if err = c.do(req, &res); err != nil {
return nil, err
}
return &res.Sound, nil

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return statements should not be cuddled if block has more than two lines (from wsl)

}

func (c *Client) UploadSoundFile(filename string) (*Sound, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported method Client.UploadSoundFile should have comment or be unexported (from golint)

f, err := os.Open(filename)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

G304: Potential file inclusion via variable (from gosec)

if err != nil {
return nil, err
}
defer f.Close() //nolint:errcheck

return c.UploadSound(filepath.Base(filename), f)
}

func (c *Client) ListSounds() ([]Sound, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported method Client.ListSounds should have comment or be unexported (from golint)

req, err := http.NewRequest("GET", "https://dialogs.yandex.net/api/v1/skills/"+c.SkillID+"/sounds", nil)
if err != nil {
return nil, err
}

var res struct {
Sounds []Sound
Total int
}
if err = c.do(req, &res); err != nil {
return nil, err
}
return res.Sounds, nil
}
60 changes: 60 additions & 0 deletions resources/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package resources

import (
"os"
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestClient(t *testing.T) {
if testing.Short() {
t.Skip("-short is passed, skipping integration test")
}

skillID := os.Getenv("ALICE_TEST_SKILL_ID")
oAuthToken := os.Getenv("ALICE_TEST_OAUTH_TOKEN")
if skillID == "" || oAuthToken == "" {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only one cuddle assignment allowed before if statement (from wsl)

t.Skip("`ALICE_TEST_SKILL_ID` or `ALICE_TEST_OAUTH_TOKEN` is not set, skipping integration test")
}

c := Client{
SkillID: skillID,
OAuthToken: oAuthToken,
Indent: true,
StrictDecoder: true,
}

t.Run("Status", func(t *testing.T) {
c.Debugf = t.Logf

status, err := c.Status()
require.NoError(t, err)
assert.Equal(t, 104857600, status.Images.Quota.Total)
assert.Equal(t, 1073741824, status.Sounds.Quota.Total)
})

t.Run("Sound", func(t *testing.T) {
t.Run("UploadSoundFile", func(t *testing.T) {
c.Debugf = t.Logf

sound, err := c.UploadSoundFile(filepath.Join("..", "testdata", "go.wav"))
require.NoError(t, err)
require.NotEmpty(t, sound)
assert.NotEmpty(t, skillID, sound.ID)
assert.Equal(t, skillID, sound.SkillID)
assert.Empty(t, sound.Size)
assert.Equal(t, "go.wav", sound.OriginalName)
assert.WithinDuration(t, time.Now(), sound.CreatedAt, 5*time.Second)
assert.False(t, sound.IsProcessed)
assert.Nil(t, sound.Error)

sounds, err := c.ListSounds()
require.NoError(t, err)
assert.Empty(t, sounds)
})
})
}