453 lines
12 KiB
Go
453 lines
12 KiB
Go
// Copyright (c) Mainflux
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package api_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mainflux/mainflux"
|
|
"github.com/mainflux/mainflux/pkg/transformers/senml"
|
|
"github.com/mainflux/mainflux/pkg/uuid"
|
|
"github.com/mainflux/mainflux/readers"
|
|
"github.com/mainflux/mainflux/readers/api"
|
|
"github.com/mainflux/mainflux/readers/mocks"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
svcName = "test-service"
|
|
token = "1"
|
|
invalid = "invalid"
|
|
numOfMessages = 100
|
|
valueFields = 5
|
|
subtopic = "topic"
|
|
mqttProt = "mqtt"
|
|
httpProt = "http"
|
|
msgName = "temperature"
|
|
)
|
|
|
|
var (
|
|
v float64 = 5
|
|
vs = "value"
|
|
vb = true
|
|
vd = "dataValue"
|
|
sum float64 = 42
|
|
|
|
idProvider = uuid.New()
|
|
)
|
|
|
|
func newServer(repo readers.MessageRepository, tc mainflux.ThingsServiceClient) *httptest.Server {
|
|
mux := api.MakeHandler(repo, tc, svcName)
|
|
return httptest.NewServer(mux)
|
|
}
|
|
|
|
type testRequest struct {
|
|
client *http.Client
|
|
method string
|
|
url string
|
|
token string
|
|
body io.Reader
|
|
}
|
|
|
|
func (tr testRequest) make() (*http.Response, error) {
|
|
req, err := http.NewRequest(tr.method, tr.url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if tr.token != "" {
|
|
req.Header.Set("Authorization", tr.token)
|
|
}
|
|
|
|
return tr.client.Do(req)
|
|
}
|
|
|
|
func TestReadAll(t *testing.T) {
|
|
chanID, err := idProvider.ID()
|
|
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
|
|
pubID, err := idProvider.ID()
|
|
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
|
|
pubID2, err := idProvider.ID()
|
|
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
|
|
|
|
now := time.Now().Unix()
|
|
|
|
var messages []senml.Message
|
|
var queryMsgs []senml.Message
|
|
var valueMsgs []senml.Message
|
|
var boolMsgs []senml.Message
|
|
var stringMsgs []senml.Message
|
|
var dataMsgs []senml.Message
|
|
|
|
for i := 0; i < numOfMessages; i++ {
|
|
// Mix possible values as well as value sum.
|
|
msg := senml.Message{
|
|
Channel: chanID,
|
|
Publisher: pubID,
|
|
Protocol: mqttProt,
|
|
Time: float64(now - int64(i)),
|
|
Name: "name",
|
|
}
|
|
|
|
count := i % valueFields
|
|
switch count {
|
|
case 0:
|
|
msg.Value = &v
|
|
valueMsgs = append(valueMsgs, msg)
|
|
case 1:
|
|
msg.BoolValue = &vb
|
|
boolMsgs = append(boolMsgs, msg)
|
|
case 2:
|
|
msg.StringValue = &vs
|
|
stringMsgs = append(stringMsgs, msg)
|
|
case 3:
|
|
msg.DataValue = &vd
|
|
dataMsgs = append(dataMsgs, msg)
|
|
case 4:
|
|
msg.Sum = &sum
|
|
msg.Subtopic = subtopic
|
|
msg.Protocol = httpProt
|
|
msg.Publisher = pubID2
|
|
msg.Name = msgName
|
|
queryMsgs = append(queryMsgs, msg)
|
|
}
|
|
|
|
messages = append(messages, msg)
|
|
}
|
|
|
|
svc := mocks.NewThingsService()
|
|
repo := mocks.NewMessageRepository(chanID, fromSenml(messages))
|
|
ts := newServer(repo, svc)
|
|
defer ts.Close()
|
|
|
|
cases := []struct {
|
|
desc string
|
|
req string
|
|
url string
|
|
token string
|
|
status int
|
|
res pageRes
|
|
}{
|
|
{
|
|
desc: "read page with valid offset and limit",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(messages)),
|
|
Messages: messages[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with negative offset",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=-1&limit=10", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with negative limit",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=-10", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with zero limit",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=0", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with non-integer offset",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=abc&limit=10", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with non-integer limit",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=abc", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with invalid channel id",
|
|
url: fmt.Sprintf("%s/channels//messages?offset=0&limit=10", ts.URL),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with invalid token",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID),
|
|
token: invalid,
|
|
status: http.StatusForbidden,
|
|
},
|
|
{
|
|
desc: "read page with multiple offset",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=0&offset=1&limit=10", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with multiple limit",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=20&limit=10", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with empty token",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID),
|
|
token: "",
|
|
status: http.StatusForbidden,
|
|
},
|
|
{
|
|
desc: "read page with default offset",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?limit=10", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(messages)),
|
|
Messages: messages[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with default limit",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?offset=0", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(messages)),
|
|
Messages: messages[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with senml fornat",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?format=messages", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(messages)),
|
|
Messages: messages[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with subtopic",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, chanID, subtopic, httpProt),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(queryMsgs)),
|
|
Messages: queryMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with subtopic and protocol",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, chanID, subtopic, httpProt),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(queryMsgs)),
|
|
Messages: queryMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with publisher",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?publisher=%s", ts.URL, chanID, pubID2),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(queryMsgs)),
|
|
Messages: queryMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with protocol",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?protocol=http", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(queryMsgs)),
|
|
Messages: queryMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with name",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?name=%s", ts.URL, chanID, msgName),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(queryMsgs)),
|
|
Messages: queryMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with value",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?v=%f", ts.URL, chanID, v),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(valueMsgs)),
|
|
Messages: valueMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with value and equal comparator",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v, readers.EqualKey),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(valueMsgs)),
|
|
Messages: valueMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with value and lower-than comparator",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v+1, readers.LowerThanKey),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(valueMsgs)),
|
|
Messages: valueMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with value and lower-than-or-equal comparator",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v+1, readers.LowerThanEqualKey),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(valueMsgs)),
|
|
Messages: valueMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with value and greater-than comparator",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v-1, readers.GreaterThanKey),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(valueMsgs)),
|
|
Messages: valueMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with value and greater-than-or-equal comparator",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v-1, readers.GreaterThanEqualKey),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(valueMsgs)),
|
|
Messages: valueMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with non-float value",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?v=ab01", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with value and wrong comparator",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=wrong", ts.URL, chanID, v-1),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with boolean value",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?vb=true", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(boolMsgs)),
|
|
Messages: boolMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with non-boolean value",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?vb=yes", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with string value",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?vs=%s", ts.URL, chanID, vs),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(stringMsgs)),
|
|
Messages: stringMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with data value",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?vd=%s", ts.URL, chanID, vd),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(dataMsgs)),
|
|
Messages: dataMsgs[0:10],
|
|
},
|
|
},
|
|
{
|
|
desc: "read page with non-float from",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?from=ABCD", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
|
|
{
|
|
desc: "read page with non-float to",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?to=ABCD", ts.URL, chanID),
|
|
token: token,
|
|
status: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "read page with from/to",
|
|
url: fmt.Sprintf("%s/channels/%s/messages?from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time),
|
|
token: token,
|
|
status: http.StatusOK,
|
|
res: pageRes{
|
|
Total: uint64(len(messages[5:20])),
|
|
Messages: messages[5:15],
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
req := testRequest{
|
|
client: ts.Client(),
|
|
method: http.MethodGet,
|
|
url: tc.url,
|
|
token: tc.token,
|
|
}
|
|
res, err := req.make()
|
|
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
|
|
|
var page pageRes
|
|
json.NewDecoder(res.Body).Decode(&page)
|
|
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
|
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected %d got %d", tc.desc, tc.status, res.StatusCode))
|
|
assert.Equal(t, tc.res.Total, page.Total, fmt.Sprintf("%s: expected %d got %d", tc.desc, tc.res.Total, page.Total))
|
|
assert.ElementsMatch(t, tc.res.Messages, page.Messages, fmt.Sprintf("%s: expected body %v got %v", tc.desc, tc.res.Messages, page.Messages))
|
|
}
|
|
}
|
|
|
|
type pageRes struct {
|
|
readers.PageMetadata
|
|
Total uint64 `json:"total"`
|
|
Messages []senml.Message `json:"messages,omitempty"`
|
|
}
|
|
|
|
func fromSenml(in []senml.Message) []readers.Message {
|
|
var ret []readers.Message
|
|
for _, m := range in {
|
|
ret = append(ret, m)
|
|
}
|
|
return ret
|
|
}
|