diff --git a/README.md b/README.md index 043822c..d5b535d 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,3 @@ -# go-musthave-shortener-tpl +# cmd/shortener -Шаблон репозитория для трека «Сервис сокращения URL». - -## Начало работы - -1. Склонируйте репозиторий в любую подходящую директорию на вашем компьютере. -2. В корне репозитория выполните команду `go mod init ` (где `` — адрес вашего репозитория на GitHub без префикса `https://`) для создания модуля. - -## Обновление шаблона - -Чтобы иметь возможность получать обновления автотестов и других частей шаблона, выполните команду: - -``` -git remote add -m main template https://github.com/Yandex-Practicum/go-musthave-shortener-tpl.git -``` - -Для обновления кода автотестов выполните команду: - -``` -git fetch template && git checkout template/main .github -``` - -Затем добавьте полученные изменения в свой репозиторий. - -## Запуск автотестов - -Для успешного запуска автотестов называйте ветки `iter`, где `` — порядковый номер инкремента. Например, в ветке с названием `iter4` запустятся автотесты для инкрементов с первого по четвёртый. - -При мёрже ветки с инкрементом в основную ветку `main` будут запускаться все автотесты. - -Подробнее про локальный и автоматический запуск читайте в [README автотестов](https://github.com/Yandex-Practicum/go-autotests). +В данной директории будет содержаться код, который скомпилируется в бинарное приложение \ No newline at end of file diff --git a/cmd/shortener/main.go b/cmd/shortener/main.go index 38dd16d..4df559a 100644 --- a/cmd/shortener/main.go +++ b/cmd/shortener/main.go @@ -1,3 +1,91 @@ package main -func main() {} +import ( + "encoding/base64" + "fmt" + "github.com/gorilla/mux" + "io" + "log" + "net/http" +) + +// Словарь для хранения соответствий между сокращёнными и оригинальными URL +// TODO Создать хранилище +var urlMap = map[string]string{} + +// Перенаправляем по полной ссылке +func redirectHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "invalid request type", http.StatusBadRequest) + return + } + // Добавляем тестовое соответствие в словарь + urlMap["aHR0cH"] = "https://practicum.yandex.ru/" + // Получаем идентификатор из URL-пути + id := r.URL.Path[1:] + + // Получаем оригинальный URL из словаря + + if originalURL, found := urlMap[id]; found { + // Устанавливаем заголовок Location и возвращаем ответ с кодом 307 + w.Header().Set("Location", originalURL) + w.WriteHeader(http.StatusTemporaryRedirect) + return + } + http.Error(w, "Ссылка не найдена", http.StatusBadRequest) + +} + +func shortenURLHandler(w http.ResponseWriter, r *http.Request) { + // Читаем тело запроса (URL) + urlBytes, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "Ошибка чтения запроса", http.StatusBadRequest) + return + } + /* + contentType := r.Header.Get("Content-Type") + + if contentType != "text/plain" || contentType != "text/plain; charset=utf-8" { + http.Error(w, "invalid request type", http.StatusBadRequest) + return + } + */ + // Преобразуем в строку + url := string(urlBytes) + + // Генерируем уникальный идентификатор сокращённого URL + id := generateID(url) + + // Добавляем соответствие в словарь + urlMap[id] = url + + // Отправляем ответ с сокращённым URL + shortenedURL := fmt.Sprintf("http://localhost:8080/%s", id) + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusCreated) + if _, err := io.WriteString(w, shortenedURL); err != nil { + log.Fatal(err) + } +} + +// Простая функция для генерации уникального идентификатора +func generateID(fullURL string) string { + encodedStr := base64.URLEncoding.EncodeToString([]byte(fullURL)) + // Возвращаем первые 6 символов закодированной строки + if len(encodedStr) > 6 { + return encodedStr[:6] + } + return encodedStr +} + +func main() { + r := mux.NewRouter() + r.HandleFunc("/{idShortenURL}", redirectHandler).Methods("GET") + r.HandleFunc("/", shortenURLHandler).Methods("POST") + + err := http.ListenAndServe(":8080", r) + if err != nil { + panic(err) + } +} diff --git a/cmd/shortener/main_test.go b/cmd/shortener/main_test.go new file mode 100644 index 0000000..320746a --- /dev/null +++ b/cmd/shortener/main_test.go @@ -0,0 +1,124 @@ +package main + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func Test_generateID(t *testing.T) { + tests := []struct { + name string + fullURL string + want string + }{ + {name: "Проверим генератор на пустую строку", fullURL: "", want: ""}, + {name: "Проверим генератор на НЕпустую строку", fullURL: "https://practicum.yandex.ru/", want: "aHR0cH"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := generateID(tt.fullURL); got != tt.want { + t.Errorf("generateID() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_shortenURLHandler(t *testing.T) { + type want struct { + code int + response string + contentType string + } + + tests := []struct { + name string + bodyURL string + responseContentType string + targetURL string + want want + }{ + {name: "positive POST shortenURLHandler", + bodyURL: "https://practicum.yandex.ru/", + responseContentType: "text/plain", + targetURL: "/", + want: want{ + code: 201, + response: `http://localhost:8080/aHR0cH`, + contentType: "text/plain", + }, + }, + /* + {name: "negative POST shortenURLHandler", + bodyURL: "https://practicum.yandex.ru/", + responseContentType: "application/json", + targetURL: "/", + want: want{ + code: 201, + response: "http://localhost:8080/aHR0cH", + contentType: "text/plain; charset=utf-8", + }, + },*/ + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + bodyURLTesting := strings.NewReader(test.bodyURL) + request := httptest.NewRequest(http.MethodPost, test.targetURL, bodyURLTesting) + request.Header.Add("Content-Type", test.responseContentType) + // создаём новый Recorder + w := httptest.NewRecorder() + shortenURLHandler(w, request) + res := w.Result() + // проверяем код ответа + assert.Equal(t, res.StatusCode, test.want.code) + // получаем и проверяем тело запроса + defer res.Body.Close() + resBody, err := io.ReadAll(res.Body) + + require.NoError(t, err) + assert.Equal(t, string(resBody), test.want.response) + assert.Equal(t, res.Header.Get("Content-Type"), test.want.contentType) + }) + } +} + +func Test_redirectHandler(t *testing.T) { + type want struct { + code int + response string + } + tests := []struct { + name string + want want + }{ + { + name: "Проверяем redirect", + want: want{ + code: 307, + response: `https://practicum.yandex.ru/`, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, "/aHR0cH", nil) + // создаём новый Recorder + w := httptest.NewRecorder() + redirectHandler(w, request) + + res := w.Result() + // проверяем код ответа + assert.Equal(t, res.StatusCode, test.want.code) + // получаем и проверяем тело запроса + defer res.Body.Close() + resBody, err := io.ReadAll(res.Body) + + require.NoError(t, err, string(resBody)) + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3fef737 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/nabbat/23_kogorta_shotener + +go 1.21.1 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4c03bbc --- /dev/null +++ b/go.sum @@ -0,0 +1,11 @@ +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/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=