Skip to content

Commit

Permalink
test: Implement unit tests for core/app using mock adapters
Browse files Browse the repository at this point in the history
* test: Add cacher and redirection_repo mocks

* refactor: Change redirection key data type to string instead of url

* fix: Set cache after cache miss

* refactor: Change byte array data type which used in cacher port to string for convenience
  • Loading branch information
mehmetumit committed Nov 2, 2023
1 parent d8657ea commit 7236bbd
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 9 deletions.
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
module github.com/mehmetumit/dexus

go 1.21.3

require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-chi/cors v1.2.1 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/redis/go-redis/v9 v9.2.1 // indirect
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg=
github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
25 changes: 19 additions & 6 deletions internal/core/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ package app
import (
"context"
"net/url"
"time"

"github.com/mehmetumit/dexus/internal/core/ports"
)

var (
defaultCacheTTL = 10 * time.Second
)

type AppConfig struct {
Logger ports.Logger
RedirectionRepo ports.RedirectionRepo
Expand All @@ -22,9 +27,9 @@ func NewApp(cfg AppConfig) *App {
}
}

func (a *App) FindRedirect(ctx context.Context, u *url.URL) (*url.URL, error) {
func (a *App) FindRedirect(ctx context.Context, p string) (*url.URL, error) {
var dataTo string
key, err := a.Cacher.GenKey(ctx, u.String())
key, err := a.Cacher.GenKey(ctx, p)
if err != nil {
a.Logger.Error("internal key generation error:", err)
return nil, err
Expand All @@ -35,16 +40,24 @@ func (a *App) FindRedirect(ctx context.Context, u *url.URL) (*url.URL, error) {
a.Logger.Error("internal cache error:", err)
return nil, err
}
a.Logger.Debug("Cache miss:", string(cachedTo))
redirectionTo, err := a.RedirectionRepo.Get(ctx, u.String())
a.Logger.Debug("Cache miss:", p)
redirectionTo, err := a.RedirectionRepo.Get(ctx, p)
if err != nil {
a.Logger.Error("internal redirection repo error:", err)
if err == ports.ErrRedirectionNotFound {
a.Logger.Error("redirection not found on repo:", err)
} else {
a.Logger.Error("internal cache error:", err)
}
return nil, err
}
//Set cache after cache miss
if err := a.Cacher.Set(ctx, key, redirectionTo, defaultCacheTTL); err != nil {
a.Logger.Error("cache set error:", err)
}
dataTo = redirectionTo

} else {
dataTo = string(cachedTo)
dataTo = cachedTo
a.Logger.Debug("Cache hit", dataTo)
}
to, err := url.Parse(dataTo)
Expand Down
86 changes: 86 additions & 0 deletions internal/core/app/app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package app

import (
"context"
"testing"

"github.com/mehmetumit/dexus/internal/core/ports"
"github.com/mehmetumit/dexus/internal/mocks"
)

func newTestApp(t testing.TB) *App {
t.Helper()
mockLogger := mocks.NewMockLogger()
mockRedirectionRepo := mocks.NewMockRedirectionRepo()
mockCacher := mocks.NewMockCacher()
mockLogger.SetDebugLevel(true)
return NewApp(
AppConfig{
Logger: mockLogger,
RedirectionRepo: mockRedirectionRepo,
Cacher: mockCacher,
},
)
}

func TestApp_FindRedirect(t *testing.T) {
redirectionMap := mocks.MockRedirectionMap{
"test1": "https://test1.com",
"test2": "https://test2.com",
"test3": "https://test3.com",
}
notFoundKeys := []string{"not-found", "not/found", ""}
t.Run("Find Redirect From Repo", func(t *testing.T) {
app := newTestApp(t)
ctx := context.Background()
app.RedirectionRepo.(*mocks.MockRedirectionRepo).SetMockRedirectionMap(redirectionMap)
for k, v := range redirectionMap {
u, err := app.FindRedirect(ctx, k)
if err != nil {
t.Errorf("Expected err nil, got %v", err)
}

if u.String() != v {
t.Errorf("Expected redirection %v, got %v", v, u.String())

}
}
})
t.Run("Find Not Found Redirect From Repo", func(t *testing.T) {
app := newTestApp(t)
app.RedirectionRepo.(*mocks.MockRedirectionRepo).SetMockRedirectionMap(redirectionMap)
for _, v := range notFoundKeys {
_, err := app.FindRedirect(context.Background(), v)
if err != ports.ErrRedirectionNotFound {
t.Errorf("Expected err %v, got %v", ports.ErrRedirectionNotFound, err)
}
}
})
t.Run("Find Redirect From Cache", func(t *testing.T) {
app := newTestApp(t)
app.RedirectionRepo.(*mocks.MockRedirectionRepo).SetMockRedirectionMap(redirectionMap)
ctx := context.Background()
for k := range redirectionMap {
app.FindRedirect(ctx, k)
}
for k, v := range redirectionMap {
keyHash, err := app.Cacher.GenKey(ctx, k)
if err != nil {
t.Errorf("Expected err nil, got %v", err)
}
gotVal, err := app.Cacher.Get(ctx, keyHash)
if err != nil {
t.Errorf("Expected err nil, got %v", err)
}
if gotVal != v {
t.Errorf("Expected cache val %v, got %v", v, gotVal)
}
_, err = app.FindRedirect(ctx, k)
if err != nil {
t.Errorf("Expected err nil, got %v", err)
}
}

})

}
2 changes: 1 addition & 1 deletion internal/core/ports/app_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import (
)

type AppRunner interface {
FindRedirect(ctx context.Context, u *url.URL) (*url.URL, error)
FindRedirect(ctx context.Context, p string) (*url.URL, error)
}
4 changes: 2 additions & 2 deletions internal/core/ports/cacher.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ var (

type Cacher interface {
GenKey(ctx context.Context, s string) (string, error)
Get(ctx context.Context, key string) ([]byte, error)
Set(ctx context.Context, key string, val []byte, ttl time.Duration) error
Get(ctx context.Context, key string) (string, error)
Set(ctx context.Context, key string, val string, ttl time.Duration) error
Delete(ctx context.Context, key string) error
Flush(ctx context.Context) error
}
64 changes: 64 additions & 0 deletions internal/mocks/cacher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package mocks

import (
"context"
"sync"
"time"

"github.com/google/uuid"
"github.com/mehmetumit/dexus/internal/core/ports"
)

type MockCacheMap map[string]string
type MockCacher struct {
sync.RWMutex //Useful for concurrent nonblocking reads
cacheMap MockCacheMap
}

func NewMockCacher() *MockCacher {
return &MockCacher{
cacheMap: make(MockCacheMap),
}
}

func (mc *MockCacher) expireAfter(key string, ttl time.Duration) {
time.AfterFunc(ttl, func() {
mc.Lock()
defer mc.Unlock()
delete(mc.cacheMap, key)
})
}
func (mc *MockCacher) GenKey(ctx context.Context, s string) (string, error) {
hashKey := uuid.NewSHA1(uuid.NameSpaceOID, []byte(s)).String()
return hashKey, nil

}
func (mc *MockCacher) Get(ctx context.Context, key string) (string, error) {
mc.RLock()
defer mc.RUnlock()
val, ok := mc.cacheMap[key]
if !ok {
return "", ports.ErrKeyNotFound
}
return val, nil

}
func (mc *MockCacher) Set(ctx context.Context, key string, val string, ttl time.Duration) error {
mc.Lock()
defer mc.Unlock()
mc.cacheMap[key] = val
mc.expireAfter(key, ttl)
return nil
}
func (mc *MockCacher) Delete(ctx context.Context, key string) error {
mc.Lock()
defer mc.Unlock()
delete(mc.cacheMap, key)
return nil
}
func (mc *MockCacher) Flush(ctx context.Context) error {
mc.Lock()
defer mc.Unlock()
clear(mc.cacheMap)
return nil
}
28 changes: 28 additions & 0 deletions internal/mocks/redirection_repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package mocks

import (
"context"

"github.com/mehmetumit/dexus/internal/core/ports"
)

type MockRedirectionRepo struct {
redirectionMap MockRedirectionMap
}

type MockRedirectionMap map[string]string

func NewMockRedirectionRepo(p ...MockRedirectionMap) *MockRedirectionRepo {
return &MockRedirectionRepo{}

}
func (mr *MockRedirectionRepo) Get(ctx context.Context, from string) (string, error) {
to, ok := mr.redirectionMap[from]
if !ok {
return "", ports.ErrRedirectionNotFound
}
return to, nil
}
func (mr *MockRedirectionRepo) SetMockRedirectionMap(m MockRedirectionMap) {
mr.redirectionMap = m
}

0 comments on commit 7236bbd

Please sign in to comment.