Merge branch '7-http-url-shortener' into 'master'
Resolve "http-url-shortener" Closes #7 See merge request slon/shad-go-private!8
This commit is contained in:
commit
6a993e185c
5 changed files with 314 additions and 0 deletions
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module gitlab.com/slon/shad-go
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/go-resty/resty/v2 v2.1.0
|
||||||
github.com/spf13/cobra v0.0.5
|
github.com/spf13/cobra v0.0.5
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
go.uber.org/goleak v1.0.0
|
go.uber.org/goleak v1.0.0
|
||||||
|
|
7
go.sum
7
go.sum
|
@ -8,11 +8,15 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/go-resty/resty/v2 v2.1.0 h1:Z6IefCpUMfnvItVJaJXWv/pMiiD11So35QgwEELsldE=
|
||||||
|
github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
@ -48,6 +52,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -63,6 +69,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbO
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
99
urlshortener/README.md
Normal file
99
urlshortener/README.md
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
## urlshortener
|
||||||
|
|
||||||
|
В этой задаче нужно написать http сервер со следующим API:
|
||||||
|
|
||||||
|
* POST /shorten {"url": "\<URL\>"} -> {"key": "\<KEY\>"}
|
||||||
|
* GET /go/\<KEY\> -> 302
|
||||||
|
|
||||||
|
GET и POST - это методы HTTP. GET запрос используется для того, чтобы получать данные, а POST - чтобы добавлять и модифицировать.
|
||||||
|
|
||||||
|
В тело `/shorten` запроса будет передаваться json вида
|
||||||
|
```
|
||||||
|
{"url":"https://github.com/golang/go/wiki/CodeReviewComments"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Сервер должен ответить json'ом следующего вида:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"url": "https://github.com/golang/go/wiki/CodeReviewComments",
|
||||||
|
"key": "ed1De1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ed1De1` здесь - это сгенерированное сервисом число.
|
||||||
|
|
||||||
|
После такого `/shorten` можно делать `/go/ed1De1`.
|
||||||
|
Ответ должен иметь иметь HTTP код 302.
|
||||||
|
302 указывает на то, что запрошенный ресурс был временно перемещен на другой адрес (передаваемый в HTTP header'е `Location`).
|
||||||
|
|
||||||
|
Если открыть http://localhost:6029/go/ed1De1 в браузере, тот перенаправит на https://github.com/golang/go/wiki/CodeReviewComments.
|
||||||
|
|
||||||
|
Сервер должен слушать порт, переданный через аргумент `--port`.
|
||||||
|
|
||||||
|
### Примеры
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
```
|
||||||
|
$ urlshortener -port 6029
|
||||||
|
```
|
||||||
|
|
||||||
|
Успешное добавление URL'а (200, Content-Type: application/json):
|
||||||
|
```
|
||||||
|
$ curl -i -X POST "localhost:6029/shorten" -d '{"url":"https://github.com/golang/go/wiki/CodeReviewComments"}'
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
Date: Sat, 15 Feb 2020 23:35:26 GMT
|
||||||
|
Content-Length: 82
|
||||||
|
|
||||||
|
{"url":"https://github.com/golang/go/wiki/CodeReviewComments","key":"65ed150831"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Невалидный json (400):
|
||||||
|
```
|
||||||
|
$ curl -i -X POST "localhost:6029/shorten" -d '{"url":"https://github.com'
|
||||||
|
HTTP/1.1 400 Bad Request
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
Date: Sat, 15 Feb 2020 23:30:27 GMT
|
||||||
|
Content-Length: 16
|
||||||
|
|
||||||
|
invalid request
|
||||||
|
```
|
||||||
|
|
||||||
|
Успешный запрос (302, Location header):
|
||||||
|
```
|
||||||
|
$ curl -i -X GET "localhost:6029/go/c1464c853a"
|
||||||
|
HTTP/1.1 302 Found
|
||||||
|
Content-Type: text/html; charset=utf-8
|
||||||
|
Location: https://github.com/golang/go/wiki/CodeReviewComments
|
||||||
|
Date: Sat, 15 Feb 2020 23:25:26 GMT
|
||||||
|
Content-Length: 75
|
||||||
|
|
||||||
|
<a href="https://github.com/golang/go/wiki/CodeReviewComments">Found</a>.
|
||||||
|
```
|
||||||
|
|
||||||
|
Несуществующий key (404):
|
||||||
|
```
|
||||||
|
$ curl -i -X GET "localhost:6029/go/uaaab"
|
||||||
|
HTTP/1.1 404 Not Found
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
Date: Sat, 15 Feb 2020 23:26:48 GMT
|
||||||
|
Content-Length: 14
|
||||||
|
|
||||||
|
key not found
|
||||||
|
```
|
||||||
|
|
||||||
|
### Состояние
|
||||||
|
|
||||||
|
Своё состояние сервис должен целиком хранить в памяти.
|
||||||
|
Стандартный http server на каждый запрос запускает handler в отдельной горутине (https://golang.org/pkg/net/http/#Serve),
|
||||||
|
поэтому доступ к состоянию нужно защитить. Например, это можно сделать с помощью [мьютекса](https://golang.org/pkg/sync/#Mutex).
|
||||||
|
|
||||||
|
## Ссылки
|
||||||
|
|
||||||
|
1. Пример web сервера и работы с общим состоянием: https://p.go.manytask.org/00-intro/lecture.slide#24
|
||||||
|
2. протокол HTTP: https://ru.wikipedia.org/wiki/HTTP
|
||||||
|
3. http multiplexer: https://golang.org/pkg/net/http/#ServeMux
|
||||||
|
4. десериализация json'а: https://golang.org/pkg/encoding/json/#example_Unmarshal
|
||||||
|
5. генерация случайных данных: https://golang.org/pkg/math/rand/
|
7
urlshortener/main.go
Normal file
7
urlshortener/main.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build !solution
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
}
|
200
urlshortener/main_test.go
Normal file
200
urlshortener/main_test.go
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gitlab.com/slon/shad-go/tools/testtool"
|
||||||
|
)
|
||||||
|
|
||||||
|
const importPath = "gitlab.com/slon/shad-go/urlshortener"
|
||||||
|
|
||||||
|
var binCache testtool.BinCache
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(func() int {
|
||||||
|
var teardown testtool.CloseFunc
|
||||||
|
binCache, teardown = testtool.NewBinCache()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
return m.Run()
|
||||||
|
}())
|
||||||
|
}
|
||||||
|
|
||||||
|
func startServer(t *testing.T) (port string, stop func()) {
|
||||||
|
binary, err := binCache.GetBinary(importPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
port, err = testtool.GetFreePort()
|
||||||
|
require.NoError(t, err, "unable to get free port")
|
||||||
|
|
||||||
|
cmd := exec.Command(binary, "-port", port)
|
||||||
|
cmd.Stdout = nil
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
require.NoError(t, cmd.Start())
|
||||||
|
|
||||||
|
done := make(chan error)
|
||||||
|
go func() {
|
||||||
|
done <- cmd.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
stop = func() {
|
||||||
|
_ = cmd.Process.Kill()
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = testtool.WaitForPort(t, time.Second*5, port); err != nil {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(t *testing.T, c *resty.Client, shortenerURL, request string) string {
|
||||||
|
type Response struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.R().
|
||||||
|
SetBody(map[string]interface{}{"url": request}).
|
||||||
|
SetResult(&Response{}).
|
||||||
|
Post(shortenerURL)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, resp.StatusCode())
|
||||||
|
response := resp.Result().(*Response)
|
||||||
|
require.Equal(t, request, response.URL)
|
||||||
|
require.Contains(t, resp.Header().Get("Content-Type"), "application/json")
|
||||||
|
|
||||||
|
return response.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLShortener_redirect(t *testing.T) {
|
||||||
|
port, stop := startServer(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
redirects := make(map[string]struct{})
|
||||||
|
|
||||||
|
redirectTarget := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
redirects[r.RequestURI] = struct{}{}
|
||||||
|
mu.Unlock()
|
||||||
|
_, _ = fmt.Fprintln(w, "hello")
|
||||||
|
}))
|
||||||
|
|
||||||
|
client := resty.New()
|
||||||
|
addURL := fmt.Sprintf("http://localhost:%s/shorten", port)
|
||||||
|
|
||||||
|
requests := make(map[string]struct{})
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
path := "/" + testtool.RandomName()
|
||||||
|
req := redirectTarget.URL + path
|
||||||
|
requests[path] = struct{}{}
|
||||||
|
key := add(t, client, addURL, req)
|
||||||
|
|
||||||
|
getURL := fmt.Sprintf("http://localhost:%s/go/%s", port, key)
|
||||||
|
resp, err := client.R().Get(getURL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, resp.StatusCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
require.True(t, reflect.DeepEqual(requests, redirects),
|
||||||
|
fmt.Sprintf("expected: %+v, got: %+v", requests, redirects))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLShortener_badRequest(t *testing.T) {
|
||||||
|
port, stop := startServer(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
u := fmt.Sprintf("http://localhost:%s/shorten", port)
|
||||||
|
resp, err := resty.New().R().
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
SetBody([]byte(`{"url":"abc}`)).
|
||||||
|
Post(u)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusBadRequest, resp.StatusCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLShortener_badKey(t *testing.T) {
|
||||||
|
port, stop := startServer(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
u := fmt.Sprintf("http://localhost:%s/go/%s", port, testtool.RandomName())
|
||||||
|
resp, err := resty.New().
|
||||||
|
SetRedirectPolicy(resty.RedirectPolicyFunc(func(*http.Request, []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
})).R().
|
||||||
|
Get(u)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusNotFound, resp.StatusCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLShortener_consistency(t *testing.T) {
|
||||||
|
port, stop := startServer(t)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
|
get := func(originalURL, key string) {
|
||||||
|
getURL := fmt.Sprintf("http://localhost:%s/go/%s", port, key)
|
||||||
|
|
||||||
|
resp, err := client.
|
||||||
|
SetRedirectPolicy(resty.RedirectPolicyFunc(func(*http.Request, []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
})).R().
|
||||||
|
Get(getURL)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusFound, resp.StatusCode())
|
||||||
|
require.Contains(t, resp.Header().Get("Location"), originalURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
var urls []string
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
urls = append(urls, testtool.RandomName())
|
||||||
|
}
|
||||||
|
|
||||||
|
keyToURL := make(map[string]string)
|
||||||
|
urlToKey := make(map[string]string)
|
||||||
|
|
||||||
|
addURL := fmt.Sprintf("http://localhost:%s/shorten", port)
|
||||||
|
for _, u := range urls {
|
||||||
|
key := add(t, client, addURL, u)
|
||||||
|
|
||||||
|
url, ok := keyToURL[key]
|
||||||
|
require.False(t, ok, fmt.Sprintf("duplicate key %s for urls [%s, %s]", key, u, url))
|
||||||
|
|
||||||
|
urlToKey[u] = key
|
||||||
|
keyToURL[key] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range urls {
|
||||||
|
get(u, urlToKey[u])
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range urls {
|
||||||
|
key := add(t, client, addURL, u)
|
||||||
|
|
||||||
|
url, ok := keyToURL[key]
|
||||||
|
require.True(t, ok, fmt.Sprintf("different keys for the same url %s", u))
|
||||||
|
require.Equal(t, url, u)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue