Compare commits

..

4 Commits

Author SHA1 Message Date
Dušan Borovčanin 2dc7f3eea1 NOISSUE - Raise endpoint test coverage (#240)
Ramove unnecessary checks from decodeList.
Cover some edge cases and fix decodeList to always return same error.

Signed-off-by: Dušan Borovčanin <borovcanindusan1@gmail.com>
2018-04-23 15:17:30 +02:00
Aleksandar Novaković 62297fedec MF-134 - Evaluate system's performance (#225)
* Add initial load tests

Add initial load tests for client creation and message publishing.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Make load tests executable as stand-alone application

Move code from test to main. Make code runnable with sbt run command.
Remove unnecessary config files.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add native packager support

Add native packager plugin. Update sbt config to support native
packager. Update paths in Engine.scala.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Move files back to test folder and remove native packager support

Remove nativa packager plugin. Add gatling plugin and move files to
test folder where they belong. Read vars from JAVA_OPTS instead of
environment variables.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add readme file for load tests

Add readme file for load tests with usage instructions.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add number of requests per second as test parameter

Add number of requests per second as parameter. Update read me according
to this addition.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add load test section in docs

Create documentation skeleton for load tests.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Reformat logger config file

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update documentation skeleton

Move results section to scenarios. Move test environment to intro.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Align test version with mainflux version

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update mainflux version to 0.2.2

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2018-04-23 15:13:43 +02:00
Dejan Mijić f449f8b9c8 NOISSUE - Fix Content-Type header check (#238)
* Remove charset from required content type

Signed-off-by: Dejan Mijic <dejan@mainflux.com>

* Bump version to 0.2.1

Signed-off-by: Dejan Mijic <dejan@mainflux.com>

* Move constant to transport.go

Signed-off-by: Dejan Mijic <dejan@mainflux.com>
2018-04-22 16:48:06 +02:00
Dejan Mijić 544ba57850 NOISSUE - Ignore logging & metrics for code coverage (#233)
Signed-off-by: Dejan Mijic <dejan@mainflux.com>
2018-04-19 13:40:22 +02:00
23 changed files with 353 additions and 75 deletions
+23
View File
@@ -0,0 +1,23 @@
## Test scenarios
Testing environment to be determined.
### Message publishing
In this scenario, large number of requests are sent to HTTP adapter service
every second. This test checks how much time HTTP adapter took to response to
each request.
#### Results
TBD
### Create and get client
In this scenario, large number of requests are sent to manager service to create
client, and than to retrieve its data. This test checks how much time manager
service took to response to each request.
#### Results
TBD
+2
View File
@@ -1,3 +1,5 @@
// +build !test
package api
import (
+2
View File
@@ -1,3 +1,5 @@
// +build !test
package api
import (
+8
View File
@@ -0,0 +1,8 @@
target/
.classpath
.cache-tests
.cache-main
.settings/
.project
*.class
bin/
+49
View File
@@ -0,0 +1,49 @@
# Load Test
This SBT project contains load tests written for mainflux platform.
## Setup
In order to run load tests you must have [openjdk8](http://openjdk.java.net/install/) and [sbt](https://www.scala-sbt.org/1.0/docs/Setup.html) installed on your machine.
## Configuration
Tests are configured to use variables from `JAVA_OPTS` presented in the
following table. Note that any unset variables will be replaced with their
default values.
| Variable | Description | Default |
|----------|------------------------------------------|-----------------------|
| manager | Manager service URL | http://localhost:8180 |
| http | HTTP adapter service URL | http://localhost:8182 |
| requests | Number of requests to be sent per second | 100 |
## Usage
This project contains two simulations:
- `PublishSimulation`
- `CreateAndRetrieveClientSimulation`
To run all tests you will have to run following commands:
```bash
cd <path_to_mainflux_project>/load-test
sbt gatling:test
```
### Publish Simulation
`PublishSimulation` contains load tests for publishing messages. To run this test use following command:
```bash
sbt "gatling:testOnly com.mainflux.loadtest.simulations.PublishSimulation"
```
### Create And Retrieve Client Simulation
`CreateAndRetrieveClientSimulation` contains load tests for creating and retrieving clients. To run this test use following command:
```bash
sbt "gatling:testOnly com.mainflux.loadtest.simulations.CreateAndRetrieveClientSimulation"
```
+18
View File
@@ -0,0 +1,18 @@
enablePlugins(GatlingPlugin)
name := "load-test"
version := "0.2.2"
scalaVersion := "2.12.4"
val gatlingVersion = "2.3.1"
val circeVersion = "0.9.3"
libraryDependencies ++= Seq(
"io.gatling.highcharts" % "gatling-charts-highcharts" % gatlingVersion,
"io.gatling" % "gatling-test-framework" % gatlingVersion,
"org.scalaj" %% "scalaj-http" % "2.3.0",
"io.circe" %% "circe-core" % circeVersion,
"io.circe" %% "circe-generic" % circeVersion,
"io.circe" %% "circe-parser" % circeVersion
)
+1
View File
@@ -0,0 +1 @@
sbt.version=1.1.2
+1
View File
@@ -0,0 +1 @@
addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.2")
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx</pattern>
</encoder>
<immediateFlush>false</immediateFlush>
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="CONSOLE" />
</appender>
<root level="WARN">
<appender-ref ref="ASYNC" />
</root>
</configuration>
@@ -0,0 +1,7 @@
package com.mainflux.loadtest.simulations
object Constants {
val ManagerUrl = System.getProperty("manager", "http://localhost:8180")
val HttpAdapterUrl = System.getProperty("http", "http://localhost:8182")
val RequestsPerSecond = Integer.getInteger("requests", 100)
}
@@ -0,0 +1,65 @@
package com.mainflux.loadtest.simulations
import scala.concurrent.duration._
import scalaj.http.Http
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
import CreateAndRetrieveClientSimulation._
import io.gatling.http.protocol.HttpProtocolBuilder.toHttpProtocol
import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder
import com.mainflux.loadtest.simulations.Constants._
class CreateAndRetrieveClientSimulation extends Simulation {
// Register user
Http(s"${ManagerUrl}/users")
.postData(User)
.header(HttpHeaderNames.ContentType, ContentType)
.asString
// Login user
val tokenRes = Http(s"${ManagerUrl}/tokens")
.postData(User)
.header(HttpHeaderNames.ContentType, ContentType)
.asString
.body
val tokenCursor = parse(tokenRes).getOrElse(Json.Null).hcursor
val token = tokenCursor.downField("token").as[String].getOrElse("")
// Prepare testing scenario
val httpProtocol = http
.baseURL(ManagerUrl)
.inferHtmlResources()
.acceptHeader("*/*")
.contentTypeHeader(ContentType)
.userAgentHeader("curl/7.54.0")
val scn = scenario("CreateAndGetClient")
.exec(http("CreateClientRequest")
.post("/clients")
.header(HttpHeaderNames.ContentType, ContentType)
.header(HttpHeaderNames.Authorization, token)
.body(StringBody(Client))
.check(status.is(201))
.check(headerRegex(HttpHeaderNames.Location, "(.*)").saveAs("location")))
.exec(http("GetClientRequest")
.get("${location}")
.header(HttpHeaderNames.Authorization, token)
.check(status.is(200)))
setUp(
scn.inject(
constantUsersPerSec(RequestsPerSecond.toDouble) during (15 second))).protocols(httpProtocol)
}
object CreateAndRetrieveClientSimulation {
val ContentType = "application/json; charset=utf-8"
val User = """{"email":"john.doe@email.com", "password":"123"}"""
val Client = """{"type":"device", "name":"weio"}"""
}
@@ -0,0 +1,98 @@
package com.mainflux.loadtest.simulations
import scala.concurrent.duration._
import scalaj.http.Http
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
import PublishSimulation._
import io.gatling.http.protocol.HttpProtocolBuilder.toHttpProtocol
import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder
import com.mainflux.loadtest.simulations.Constants._
class PublishSimulation extends Simulation {
// Register user
Http(s"${ManagerUrl}/users")
.postData(User)
.header(HttpHeaderNames.ContentType, ContentType)
.asString
// Login user
val tokenRes = Http(s"${ManagerUrl}/tokens")
.postData(User)
.header(HttpHeaderNames.ContentType, ContentType)
.asString
.body
val tokenCursor = parse(tokenRes).getOrElse(Json.Null).hcursor
val token = tokenCursor.downField("token").as[String].getOrElse("")
// Register client
val clientLocation = Http(s"${ManagerUrl}/clients")
.postData(Client)
.header(HttpHeaderNames.Authorization, token)
.header(HttpHeaderNames.ContentType, ContentType)
.asString
.headers.get("Location").get(0)
val clientId = clientLocation.split("/")(2)
// Get client key
val clientRes = Http(s"${ManagerUrl}/clients/${clientId}")
.header(HttpHeaderNames.Authorization, token)
.header(HttpHeaderNames.ContentType, ContentType)
.asString
.body
val clientCursor = parse(clientRes).getOrElse(Json.Null).hcursor
val clientKey = clientCursor.downField("key").as[String].getOrElse("")
// Register channel
val chanLocation = Http(s"${ManagerUrl}/channels")
.postData(Channel)
.header(HttpHeaderNames.Authorization, token)
.header(HttpHeaderNames.ContentType, ContentType)
.asString
.headers.get("Location").get(0)
val chanId = chanLocation.split("/")(2)
// Connect client to channel
Http(s"${ManagerUrl}/channels/${chanId}/clients/${clientId}")
.method("PUT")
.header(HttpHeaderNames.Authorization, token)
.asString
// Prepare testing scenario
val httpProtocol = http
.baseURL(HttpAdapterUrl)
.inferHtmlResources()
.acceptHeader("*/*")
.contentTypeHeader("application/json; charset=utf-8")
.userAgentHeader("curl/7.54.0")
val scn = scenario("PublishMessage")
.exec(http("PublishMessageRequest")
.post(s"/channels/${chanId}/messages")
.header(HttpHeaderNames.ContentType, "application/senml+json")
.header(HttpHeaderNames.Authorization, clientKey)
.body(StringBody(Message))
.check(status.is(202)))
setUp(
scn.inject(
constantUsersPerSec(RequestsPerSecond.toDouble) during (15 second))).protocols(httpProtocol)
}
object PublishSimulation {
val ContentType = "application/json; charset=utf-8"
val User = """{"email":"john.doe@email.com", "password":"123"}"""
val Client = """{"type":"device", "name":"weio"}"""
val Channel = """{"name":"mychan"}"""
val Message = """[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]"""
}
+35 -46
View File
@@ -17,7 +17,7 @@ import (
)
const (
contentType = "application/json; charset=utf-8"
contentType = "application/json"
invalidEmail = "userexample.com"
wrongID = "123e4567-e89b-12d3-a456-000000000042"
id = "123e4567-e89b-12d3-a456-000000000001"
@@ -316,28 +316,35 @@ func TestListClients(t *testing.T) {
client.Key = id
clients = append(clients, client)
}
clientURL := fmt.Sprintf("%s/clients", ts.URL)
cases := []struct {
desc string
auth string
status int
offset int
limit int
url string
res []manager.Client
}{
{"get a list of clients", user.Email, http.StatusOK, 1, 5, clients[1:6]},
{"get a list of clients with invalid token", invalidEmail, http.StatusForbidden, 0, 1, nil},
{"get a list of clients with invalid offset", user.Email, http.StatusBadRequest, -1, 5, nil},
{"get a list of clients with invalid limit", user.Email, http.StatusBadRequest, 1, -5, nil},
{"get a list of clients with zero limit", user.Email, http.StatusBadRequest, 1, 0, nil},
{"get a list of clients with limit greater than max", user.Email, http.StatusBadRequest, 0, 110, nil},
{"get a list of clients", user.Email, http.StatusOK, fmt.Sprintf("%s?offset=%d&limit=%d", clientURL, 0, 5), clients[0:5]},
{"get a list of clients with invalid token", invalidEmail, http.StatusForbidden, fmt.Sprintf("%s?offset=%d&limit=%d", clientURL, 0, 1), nil},
{"get a list of clients with invalid offset", user.Email, http.StatusBadRequest, fmt.Sprintf("%s?offset=%d&limit=%d", clientURL, -1, 5), nil},
{"get a list of clients with invalid limit", user.Email, http.StatusBadRequest, fmt.Sprintf("%s?offset=%d&limit=%d", clientURL, 1, -5), nil},
{"get a list of clients with zero limit", user.Email, http.StatusBadRequest, fmt.Sprintf("%s?offset=%d&limit=%d", clientURL, 1, 0), nil},
{"get a list of clients with no offset provided", user.Email, http.StatusOK, fmt.Sprintf("%s?limit=%d", clientURL, 5), clients[0:5]},
{"get a list of clients with no limit provided", user.Email, http.StatusOK, fmt.Sprintf("%s?offset=%d", clientURL, 1), clients[1:11]},
{"get a list of clients with redundant query params", user.Email, http.StatusOK, fmt.Sprintf("%s?offset=%d&limit=%d&value=something", clientURL, 0, 5), clients[0:5]},
{"get a list of clients with limit greater than max", user.Email, http.StatusBadRequest, fmt.Sprintf("%s?offset=%d&limit=%d", clientURL, 0, 110), nil},
{"get a list of clients with default URL", user.Email, http.StatusOK, fmt.Sprintf("%s%s", clientURL, ""), clients[0:10]},
{"get a list of clients with invalid URL", user.Email, http.StatusBadRequest, fmt.Sprintf("%s%s", clientURL, "?%%"), nil},
{"get a list of clients with invalid number of params", user.Email, http.StatusBadRequest, fmt.Sprintf("%s%s", clientURL, "?offset=4&limit=4&limit=5&offset=5"), nil},
{"get a list of clients with invalid offset", user.Email, http.StatusBadRequest, fmt.Sprintf("%s%s", clientURL, "?offset=e&limit=5"), nil},
{"get a list of clients with invalid limit", user.Email, http.StatusBadRequest, fmt.Sprintf("%s%s", clientURL, "?offset=5&limit=e"), nil},
}
for _, tc := range cases {
req := testRequest{
client: cli,
method: http.MethodGet,
url: fmt.Sprintf("%s/clients?offset=%d&limit=%d", ts.URL, tc.offset, tc.limit),
url: tc.url,
token: tc.auth,
}
res, err := req.make()
@@ -347,19 +354,6 @@ func TestListClients(t *testing.T) {
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
assert.ElementsMatch(t, tc.res, data["clients"], fmt.Sprintf("%s: expected body %s got %s", tc.desc, tc.res, data["clients"]))
}
req := testRequest{
client: cli,
method: http.MethodGet,
url: fmt.Sprintf("%s/clients", ts.URL),
token: user.Email,
}
defaults := "get a list of clients with no limit and offset params"
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", defaults, err))
var data map[string][]manager.Client
json.NewDecoder(res.Body).Decode(&data)
assert.Equal(t, http.StatusOK, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", defaults, http.StatusOK, res.StatusCode))
assert.ElementsMatch(t, clients[0:10], data["clients"], fmt.Sprintf("%s: expected body %s got %s", defaults, clients[0:10], data["clients"]))
}
func TestRemoveClient(t *testing.T) {
@@ -538,28 +532,36 @@ func TestListChannels(t *testing.T) {
channel.ID = id
channels = append(channels, channel)
}
channelURL := fmt.Sprintf("%s/channels", ts.URL)
cases := []struct {
desc string
auth string
status int
offset int
limit int
url string
res []manager.Channel
}{
{"get a list of channels", user.Email, http.StatusOK, 1, 5, channels[1:6]},
{"get a list of channels with invalid token", invalidEmail, http.StatusForbidden, 0, 1, nil},
{"get a list of channels with invalid offset", user.Email, http.StatusBadRequest, -1, 5, nil},
{"get a list of channels with invalid limit", user.Email, http.StatusBadRequest, 1, -5, nil},
{"get a list of channels with zero limit", user.Email, http.StatusBadRequest, 1, 0, nil},
{"get a list of channels with limit greater than max", user.Email, http.StatusBadRequest, 0, 110, nil},
{"get a list of channels", user.Email, http.StatusOK, fmt.Sprintf("%s?offset=%d&limit=%d", channelURL, 0, 6), channels[0:6]},
{"get a list of channels with invalid token", invalidEmail, http.StatusForbidden, fmt.Sprintf("%s?offset=%d&limit=%d", channelURL, 0, 1), nil},
{"get a list of channels with invalid offset", user.Email, http.StatusBadRequest, fmt.Sprintf("%s?offset=%d&limit=%d", channelURL, -1, 5), nil},
{"get a list of channels with invalid limit", user.Email, http.StatusBadRequest, fmt.Sprintf("%s?offset=%d&limit=%d", channelURL, -1, 5), nil},
{"get a list of channels with zero limit", user.Email, http.StatusBadRequest, fmt.Sprintf("%s?offset=%d&limit=%d", channelURL, 1, 0), nil},
{"get a list of channels with no offset provided", user.Email, http.StatusOK, fmt.Sprintf("%s?limit=%d", channelURL, 5), channels[0:5]},
{"get a list of channels with no limit provided", user.Email, http.StatusOK, fmt.Sprintf("%s?offset=%d", channelURL, 1), channels[1:11]},
{"get a list of channels with redundant query params", user.Email, http.StatusOK, fmt.Sprintf("%s?offset=%d&limit=%d&value=something", channelURL, 0, 5), channels[0:5]},
{"get a list of channels with limit greater than max", user.Email, http.StatusBadRequest, fmt.Sprintf("%s?offset=%d&limit=%d", channelURL, 0, 110), nil},
{"get a list of channels with default URL", user.Email, http.StatusOK, fmt.Sprintf("%s%s", channelURL, ""), channels[0:10]},
{"get a list of channels with invalid URL", user.Email, http.StatusBadRequest, fmt.Sprintf("%s%s", channelURL, "?%%"), nil},
{"get a list of channels with invalid number of params", user.Email, http.StatusBadRequest, fmt.Sprintf("%s%s", channelURL, "?offset=4&limit=4&limit=5&offset=5"), nil},
{"get a list of channels with invalid offset", user.Email, http.StatusBadRequest, fmt.Sprintf("%s%s", channelURL, "?offset=e&limit=5"), nil},
{"get a list of channels with invalid limit", user.Email, http.StatusBadRequest, fmt.Sprintf("%s%s", channelURL, "?offset=5&limit=e"), nil},
}
for _, tc := range cases {
req := testRequest{
client: client,
method: http.MethodGet,
url: fmt.Sprintf("%s/channels?offset=%d&limit=%d", ts.URL, tc.offset, tc.limit),
url: tc.url,
token: tc.auth,
}
res, err := req.make()
@@ -569,19 +571,6 @@ func TestListChannels(t *testing.T) {
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
assert.ElementsMatch(t, tc.res, body["channels"], fmt.Sprintf("%s: expected body %s got %s", tc.desc, tc.res, body["channels"]))
}
req := testRequest{
client: client,
method: http.MethodGet,
url: fmt.Sprintf("%s/channels", ts.URL),
token: user.Email,
}
defaults := "get a list of channels with no limit and offset params"
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", defaults, err))
var data map[string][]manager.Channel
json.NewDecoder(res.Body).Decode(&data)
assert.Equal(t, http.StatusOK, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", defaults, http.StatusOK, res.StatusCode))
assert.ElementsMatch(t, channels[0:10], data["channels"], fmt.Sprintf("%s: expected body %s got %s", defaults, channels[0:10], data["channels"]))
}
func TestRemoveChannel(t *testing.T) {
+2
View File
@@ -1,3 +1,5 @@
// +build !test
package api
import (
+2
View File
@@ -1,3 +1,5 @@
// +build !test
package api
import (
-2
View File
@@ -7,8 +7,6 @@ import (
"github.com/mainflux/mainflux/manager"
)
const contentType = "application/json; charset=utf-8"
type apiRes interface {
code() int
headers() map[string]string
+11 -20
View File
@@ -16,8 +16,12 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var errUnsupportedContentType = errors.New("unsupported content type")
var errInvalidQueryParams = errors.New("invalid query params")
const contentType = "application/json"
var (
errUnsupportedContentType = errors.New("unsupported content type")
errInvalidQueryParams = errors.New("invalid query params")
)
// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(svc manager.Service) http.Handler {
@@ -250,28 +254,13 @@ func decodeView(_ context.Context, r *http.Request) (interface{}, error) {
}
func decodeList(_ context.Context, r *http.Request) (interface{}, error) {
q, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
return nil, err
return nil, errInvalidQueryParams
}
offset := 0
limit := 10
n := len(q)
if n == 0 {
req := listResourcesReq{
key: r.Header.Get("Authorization"),
offset: offset,
limit: limit,
}
return req, nil
}
if n > 2 {
return nil, errInvalidQueryParams
}
off, lmt := q["offset"], q["limit"]
if len(off) > 1 || len(lmt) > 1 {
@@ -281,14 +270,14 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) {
if len(off) == 1 {
offset, err = strconv.Atoi(off[0])
if err != nil {
return nil, err
return nil, errInvalidQueryParams
}
}
if len(lmt) == 1 {
limit, err = strconv.Atoi(lmt[0])
if err != nil {
return nil, err
return nil, errInvalidQueryParams
}
}
req := listResourcesReq{
@@ -342,6 +331,8 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.WriteHeader(http.StatusConflict)
case errUnsupportedContentType:
w.WriteHeader(http.StatusUnsupportedMediaType)
case errInvalidQueryParams:
w.WriteHeader(http.StatusBadRequest)
case io.ErrUnexpectedEOF:
w.WriteHeader(http.StatusBadRequest)
case io.EOF:
+4 -3
View File
@@ -71,11 +71,12 @@ func (crm *channelRepositoryMock) All(owner string, offset, limit int) []manager
return channels
}
first := fmt.Sprintf("%s%012d", chanId, offset)
last := fmt.Sprintf("%s%012d", chanId, offset+limit)
// Since IDs starts from 1, shift everything by one.
first := fmt.Sprintf("%s%012d", chanId, offset+1)
last := fmt.Sprintf("%s%012d", chanId, offset+limit+1)
for k, v := range crm.channels {
if strings.HasPrefix(k, prefix) && v.ID > first && v.ID <= last {
if strings.HasPrefix(k, prefix) && v.ID >= first && v.ID < last {
channels = append(channels, v)
}
}
+4 -3
View File
@@ -75,11 +75,12 @@ func (crm *clientRepositoryMock) All(owner string, offset, limit int) []manager.
return clients
}
first := fmt.Sprintf("%s%012d", cliId, offset)
last := fmt.Sprintf("%s%012d", cliId, offset+limit)
// Since IDs start from 1, shift everything by one.
first := fmt.Sprintf("%s%012d", cliId, offset+1)
last := fmt.Sprintf("%s%012d", cliId, offset+limit+1)
for k, v := range crm.clients {
if strings.HasPrefix(k, prefix) && v.ID > first && v.ID <= last {
if strings.HasPrefix(k, prefix) && v.ID >= first && v.ID < last {
clients = append(clients, v)
}
}
+1
View File
@@ -41,3 +41,4 @@ pages:
- License: LICENSE.txt
- Architecture: architecture.md
- Getting started: getting-started.md
- Load test: load-test.md
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"net/http"
)
const version string = "0.2.0"
const version string = "0.2.2"
type response struct {
Version string
+2
View File
@@ -1,3 +1,5 @@
// +build !test
package api
import (
+2
View File
@@ -1,3 +1,5 @@
// +build !test
package api
import (