Skip to content

Commit

Permalink
Added first version of repository
Browse files Browse the repository at this point in the history
  • Loading branch information
totemcaf committed Jun 24, 2022
1 parent cf56657 commit 99e5192
Show file tree
Hide file tree
Showing 2 changed files with 281 additions and 0 deletions.
115 changes: 115 additions & 0 deletions repositories/in_memory_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package repositories

import (
"errors"
"sync"

"github.com/totemcaf/gollections/lists"
"github.com/totemcaf/gollections/maps"
"github.com/totemcaf/gollections/types"
)

var invalidKey = errors.New("invalid key, nil")
var notFound = errors.New("not found")
var duplicateKey = errors.New("key is duplicated")

type InMemoryRepository[Key comparable, Entity any] struct {
elementsById map[Key]Entity
emptyKey Key
lock sync.RWMutex
AllowEmptyKey bool
GetKey func(Entity) Key
}

func (r *InMemoryRepository[Key, Entity]) init() {
if r.elementsById == nil {
r.elementsById = make(map[Key]Entity, 16)
}
}

func (r *InMemoryRepository[Key, Entity]) Create(entity Entity) (Entity, error) {
r.lock.Lock()
defer r.lock.Unlock()
r.init()

key := r.GetKey(entity)
if !r.AllowEmptyKey && key == r.emptyKey {
return entity, invalidKey
}

_, alreadyInMap := r.elementsById[key]
if alreadyInMap {
return entity, duplicateKey
}

r.elementsById[key] = entity
return entity, nil
}

func (r *InMemoryRepository[Key, Entity]) Update(entity Entity) (Entity, error) {
r.lock.Lock()
defer r.lock.Unlock()
r.init()

key := r.GetKey(entity)
if !r.AllowEmptyKey && key == r.emptyKey {
return entity, invalidKey
}

_, alreadyInMap := r.elementsById[key]
if !alreadyInMap {
return entity, notFound
}

r.elementsById[key] = entity
return entity, nil
}

func (r *InMemoryRepository[Key, Entity]) Delete(key Key) error {
r.lock.Lock()
defer r.lock.Unlock()
r.init()

if !r.AllowEmptyKey && key == r.emptyKey {
return invalidKey
}

_, alreadyInMap := r.elementsById[key]

if !alreadyInMap {
return notFound
}

delete(r.elementsById, key)

return nil
}

func (r *InMemoryRepository[Key, Entity]) FindById(key Key) (Entity, bool) {
r.lock.RLock()
defer r.lock.RUnlock()
r.init()

if !r.AllowEmptyKey && key == r.emptyKey {
var entity Entity
return entity, false
}

if entity, found := r.elementsById[key]; found {
return entity, true
}
var empty Entity
return empty, false
}

func (r *InMemoryRepository[Key, Entity]) FindBy(predicate types.Predicate[Entity]) []Entity {
r.lock.RLock()
defer r.lock.RUnlock()

// This is not the most efficient way to do it, but this repository is meant for tests
return lists.Filter(maps.Values(r.elementsById), predicate)
}

func (r *InMemoryRepository[Key, Entity]) TotalCount() int {
return len(r.elementsById)
}
166 changes: 166 additions & 0 deletions repositories/in_memory_repository_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package repositories

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

const Key1 = "key-1"

type entity struct {
Id string
Value int
}

func TestNew_Is_is_empty(t *testing.T) {
repo := newRepo()

count := repo.TotalCount()

assert.Equal(t, 0, count)
}

func newRepo() *InMemoryRepository[string, *entity] {
return &InMemoryRepository[string, *entity]{
GetKey: func(e *entity) string { return e.Id },
}
}

func Test_added_element_it_is_found(t *testing.T) {
repo := newRepo()

toStore := &entity{Key1, 42}

_, _ = repo.Create(toStore)

entity, found := repo.FindById(Key1)
assert.True(t, found)
assert.Equal(t, toStore, entity)
}

func Test_added_element_is_returned(t *testing.T) {
repo := newRepo()

toStore := &entity{Key1, 42}

stored, err := repo.Create(toStore)

if !assert.Nil(t, err) {
assert.Equal(t, toStore, stored)
}
}

func Test_added_element_is_counted(t *testing.T) {
repo := newRepo()

toStore := &entity{Key1, 42}
_, _ = repo.Create(toStore)

count := repo.TotalCount()

assert.Equal(t, 1, count)
}

func Test_added_elements_are_counted(t *testing.T) {
repo := newRepo()

for idx := 1; idx <= 10; idx++ {
_, _ = repo.Create(&entity{fmt.Sprintf("key-%d", idx), 42*1000 + idx})
}

count := repo.TotalCount()

assert.Equal(t, 10, count)
}

func Test_cannot_add_element_with_same_key(t *testing.T) {
repo := newRepo()
_, _ = repo.Create(&entity{Key1, 42})

_, err := repo.Create(&entity{Key1, 42})

assert.ErrorIs(t, err, duplicateKey)
}

func Test_cannot_add_element_with_empty_key(t *testing.T) {
repo := newRepo()

_, err := repo.Create(&entity{"", 42})

assert.ErrorIs(t, err, invalidKey)
}

func Test_Update_returns_replaced_entity(t *testing.T) {
repo := newRepo()
_, _ = repo.Create(&entity{Key1, 42})

entity, err := repo.Update(&entity{Key1, 4242})

assert.Nil(t, err)
assert.Equal(t, 4242, entity.Value)
}

func Test_Update_replace_entity(t *testing.T) {
repo := newRepo()
_, _ = repo.Create(&entity{Key1, 42})
_, _ = repo.Update(&entity{Key1, 4242})

entity, found := repo.FindById(Key1)

assert.True(t, found)
assert.Equal(t, Key1, entity.Id)
assert.Equal(t, 4242, entity.Value)
}

func Test_FindBy_founds_entities(t *testing.T) {
repo := newRepo()
_, _ = repo.Create(&entity{"a-key-001", 4200})
_, _ = repo.Create(&entity{"a-key-002", 42})
_, _ = repo.Create(&entity{"a-key-003", 35})
_, _ = repo.Create(&entity{"a-key-004", 179})

greaterThan100 := func(e *entity) bool { return e.Value > 100 }

entities := repo.FindBy(greaterThan100)

assert.Len(t, entities, 2)

expected := []*entity{
{"a-key-001", 4200},
{"a-key-004", 179},
}

allEquals(t, expected, entities)
}

func allEquals[T any](t *testing.T, expected []T, entities []T) bool {
if !assert.Len(t, entities, len(expected)) {
return false
}

for idx, e := range entities {
if !assert.Equal(t, expected[idx], e) {
return false
}
}
return true
}

func Test_Delete_reduce_count(t *testing.T) {
repo := newRepo()

for idx := 1; idx <= 10; idx++ {
_, _ = repo.Create(&entity{fmt.Sprintf("key-%d", idx), 42*1000 + idx})
}

previousCount := repo.TotalCount()

_ = repo.Delete("key-3")
_ = repo.Delete("key-5")

count := repo.TotalCount()

assert.Equal(t, previousCount-2, count)
}

0 comments on commit 99e5192

Please sign in to comment.