Skip to content

Commit

Permalink
Merge pull request #23 from MowlCoder/iter23
Browse files Browse the repository at this point in the history
Iter23
  • Loading branch information
MowlCoder committed Dec 15, 2023
2 parents 33525fb + 41029f5 commit c8bbaeb
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 16 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ vendor/

shortenertest
shortenertestbeta
bin
bin

certs
53 changes: 48 additions & 5 deletions cmd/shortener/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"log"
"math/rand"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/go-chi/chi/v5"
Expand Down Expand Up @@ -76,15 +79,55 @@ func main() {
gzipWriter,
)

go deleteURLQueue.Start(context.Background())
workersCtx, workersStopCtx := context.WithCancel(context.Background())
go deleteURLQueue.Start(workersCtx)

displayBuildInfo()
fmt.Println("URL Shortener server is running on", appConfig.BaseHTTPAddr)
fmt.Println("Config:", appConfig)
log.Println("URL Shortener server is running on", appConfig.BaseHTTPAddr)
log.Println("Config:", appConfig)

if err := http.ListenAndServe(appConfig.BaseHTTPAddr, router); err != nil {
panic(err)
server := http.Server{
Addr: appConfig.BaseHTTPAddr,
Handler: router,
}

go func() {
var err error

if appConfig.EnableHTTPS {
err = server.ListenAndServeTLS(appConfig.SSLPemPath, appConfig.SSLKeyPath)
} else {
err = server.ListenAndServe()
}

if err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGINT)
<-sigs

log.Println("start graceful shutdown...")

shutdownCtx, shutdownCtxCancel := context.WithTimeout(context.Background(), 60*time.Second)
defer shutdownCtxCancel()

go func() {
<-shutdownCtx.Done()
if shutdownCtx.Err() == context.DeadlineExceeded {
log.Fatal("graceful shutdown timed out... forcing exit")
}
}()

if err := server.Shutdown(shutdownCtx); err != nil {
log.Fatal(err)
}

workersStopCtx()

log.Println("graceful shutdown server successfully")
}

// @title URL shortener
Expand Down
38 changes: 33 additions & 5 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package config

import (
"encoding/json"
"flag"
"fmt"
"log"
"os"

"github.com/caarlos0/env/v9"
)

// AppConfig store configuration for http server, storage (file or database)
// and configurable variables for application.
type AppConfig struct {
BaseHTTPAddr string `env:"SERVER_ADDRESS"`
BaseShortURLAddr string `env:"BASE_URL"`
AppEnvironment string `env:"APP_ENV"`
FileStoragePath string `env:"FILE_STORAGE_PATH"`
DatabaseDSN string `env:"DATABASE_DSN"`
BaseHTTPAddr string `env:"SERVER_ADDRESS" json:"base_http_addr"`
BaseShortURLAddr string `env:"BASE_URL" json:"base_url"`
AppEnvironment string `env:"APP_ENV" json:"app_env"`
FileStoragePath string `env:"FILE_STORAGE_PATH" json:"file_storage_path"`
DatabaseDSN string `env:"DATABASE_DSN" json:"database_dsn"`
EnableHTTPS bool `env:"ENABLE_HTTPS" json:"enable_https"`
SSLKeyPath string `env:"SSL_KEY_PATH" json:"ssl_key_path"`
SSLPemPath string `env:"SSL_PEM_PATH" json:"ssl_pem_path"`
}

// Available environments.
Expand All @@ -25,10 +31,32 @@ const (

// ParseFlags firstly parse flags and set defaults, after try to parse variables from environment variables.
func (appConfig *AppConfig) ParseFlags() {
var configPath string
flag.StringVar(&configPath, "c", "", "Path to config file")
flag.StringVar(&appConfig.BaseHTTPAddr, "a", "localhost:8080", "Base http address that server running on")
flag.StringVar(&appConfig.BaseShortURLAddr, "b", "http://localhost:8080", "Base short url address")
flag.StringVar(&appConfig.FileStoragePath, "f", "/tmp/short-url-db.json", "Storage file path")
flag.StringVar(&appConfig.DatabaseDSN, "d", "", "Database DSN")
flag.BoolVar(&appConfig.EnableHTTPS, "s", false, "Enable HTTPS")
flag.StringVar(&appConfig.SSLKeyPath, "sslk", "./certs/server.key", "Path to ssl key file")
flag.StringVar(&appConfig.SSLPemPath, "sslp", "./certs/server.pem", "Path to ssl pem file")
flag.Parse()

if configPathFromEnv, ok := os.LookupEnv("CONFIG"); ok {
configPath = configPathFromEnv
}

if configPath != "" {
rawContent, err := os.ReadFile(configPath)
if err != nil {
log.Fatal(err)
}

if err := json.Unmarshal(rawContent, appConfig); err != nil {
log.Fatal(err)
}
}

flag.Parse()

if err := env.Parse(appConfig); err != nil {
Expand Down
9 changes: 7 additions & 2 deletions internal/jwt/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import (
"github.com/golang-jwt/jwt/v4"
)

const (
defaultJWTSecret = "secret"
envKeyJWTSecret = "JWT_SECRET"
)

// Claims is JWT payload.
type Claims struct {
jwt.RegisteredClaims
Expand Down Expand Up @@ -50,10 +55,10 @@ func ParseToken(tokenString string) (*Claims, error) {
}

func getJWTSecretKey() string {
key, ok := os.LookupEnv("JWT_SECRET")
key, ok := os.LookupEnv(envKeyJWTSecret)

if !ok {
return "secret"
return defaultJWTSecret
}

return key
Expand Down
25 changes: 25 additions & 0 deletions internal/jwt/jwt_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jwt

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -35,6 +36,30 @@ func TestParseToken(t *testing.T) {

assert.Equal(t, claims.UserID, userID)
})

t.Run("invalid token", func(t *testing.T) {
_, err := ParseToken("invalid token")
require.Error(t, err)
})
}

func TestGetJWTSecretKey(t *testing.T) {
t.Run("get secret from env (exist in env)", func(t *testing.T) {
secret := "super-secret-jwt-secret"
err := os.Setenv(envKeyJWTSecret, secret)
require.NoError(t, err)
secretFromENV := getJWTSecretKey()

assert.Equal(t, secretFromENV, secret)
})

t.Run("get secret from env (no exist in env)", func(t *testing.T) {
err := os.Unsetenv(envKeyJWTSecret)
require.NoError(t, err)
secretFromENV := getJWTSecretKey()

assert.Equal(t, secretFromENV, defaultJWTSecret)
})
}

func BenchmarkParseToken(b *testing.B) {
Expand Down
101 changes: 98 additions & 3 deletions internal/storage/file_storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,92 @@ func TestFileStorage_SaveURL(t *testing.T) {
})
}

func TestFileStorage_GetURLsByUserID(t *testing.T) {
type TestCase struct {
Name string
UserID string
PrepareStorage func() *FileStorage
IsError bool
ExpectedLen int
}

testCases := []TestCase{
{
Name: "Get urls (valid)",
UserID: "123",
PrepareStorage: func() *FileStorage {
storage, _ := NewFileStorage("")
storage.structure["1"] = models.ShortenedURL{
ID: 1,
OriginalURL: "123",
UserID: "123",
ShortURL: "123",
}

return storage
},
IsError: false,
ExpectedLen: 1,
},
{
Name: "Get urls (zero)",
UserID: "123",
PrepareStorage: func() *FileStorage {
storage, _ := NewFileStorage("")
return storage
},
IsError: false,
ExpectedLen: 0,
},
}

for _, testCase := range testCases {
storage := testCase.PrepareStorage()
urls, err := storage.GetURLsByUserID(context.Background(), testCase.UserID)

if testCase.IsError {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Len(t, urls, testCase.ExpectedLen)
}
}
}

func TestFileStorage_SaveSeveralURL(t *testing.T) {
type TestCase struct {
Name string
DTOs []domain.SaveShortURLDto
IsError bool
}

testCases := []TestCase{
{
Name: "Save URLs",
IsError: false,
DTOs: []domain.SaveShortURLDto{
{
OriginalURL: "123",
ShortURL: "123",
UserID: "123",
},
},
},
}

for _, testCase := range testCases {
storage, _ := NewFileStorage("")
urls, err := storage.SaveSeveralURL(context.Background(), testCase.DTOs)

if testCase.IsError {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Len(t, urls, len(testCase.DTOs))
}
}
}

func TestFileStorage_GetOriginalURLByShortURL(t *testing.T) {
t.Run("Get url", func(t *testing.T) {
testID := "testid"
Expand Down Expand Up @@ -87,7 +173,7 @@ func TestFileStorage_DeleteByShortURLs(t *testing.T) {
testURL := "https://test.com"
userID := "32"

storage, _ := NewInMemoryStorage()
storage, _ := NewFileStorage("")
storage.structure[testID] = models.ShortenedURL{
ShortURL: testID,
OriginalURL: testURL,
Expand All @@ -108,7 +194,7 @@ func TestFileStorage_DeleteByShortURLs(t *testing.T) {
testURL := "https://test.com"
userID := "32"

storage, _ := NewInMemoryStorage()
storage, _ := NewFileStorage("")
storage.structure[testID1] = models.ShortenedURL{
ShortURL: testID1,
OriginalURL: testURL,
Expand Down Expand Up @@ -144,7 +230,7 @@ func TestFileStorage_DeleteByShortURLs(t *testing.T) {
userIDMy := "32"
userIDOther := "33"

storage, _ := NewInMemoryStorage()
storage, _ := NewFileStorage("")
storage.structure[testID] = models.ShortenedURL{
ShortURL: testID,
OriginalURL: testURL,
Expand All @@ -158,3 +244,12 @@ func TestFileStorage_DeleteByShortURLs(t *testing.T) {
assert.Equal(t, storage.structure[testID].IsDeleted, false)
})
}

func TestFileStorage_Ping(t *testing.T) {
storage, _ := NewFileStorage("")

t.Run("valid ping", func(t *testing.T) {
err := storage.Ping(context.Background())
assert.NoError(t, err)
})
}

0 comments on commit c8bbaeb

Please sign in to comment.