feat: domain finished
This commit is contained in:
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
Generated
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/clean-architecture-go-v2.iml" filepath="$PROJECT_DIR$/.idea/clean-architecture-go-v2.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+83
@@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectTasksOptions">
|
||||||
|
<TaskOptions isEnabled="true">
|
||||||
|
<option name="arguments" value="fmt $FilePath$" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="ERROR" />
|
||||||
|
<option name="fileExtension" value="go" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="go fmt" />
|
||||||
|
<option name="output" value="$FilePath$" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array />
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="$GoExecPath$" />
|
||||||
|
<option name="runOnExternalChanges" value="false" />
|
||||||
|
<option name="scopeName" value="Current File" />
|
||||||
|
<option name="trackOnlyRoot" value="true" />
|
||||||
|
<option name="workingDir" value="$ProjectFileDir$" />
|
||||||
|
<envs>
|
||||||
|
<env name="GOROOT" value="$GOROOT$" />
|
||||||
|
<env name="GOPATH" value="$GOPATH$" />
|
||||||
|
<env name="PATH" value="$GoBinDirs$" />
|
||||||
|
</envs>
|
||||||
|
</TaskOptions>
|
||||||
|
<TaskOptions isEnabled="true">
|
||||||
|
<option name="arguments" value="-w $FilePath$" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="ERROR" />
|
||||||
|
<option name="fileExtension" value="go" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="goimports" />
|
||||||
|
<option name="output" value="$FilePath$" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array />
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="goimports" />
|
||||||
|
<option name="runOnExternalChanges" value="false" />
|
||||||
|
<option name="scopeName" value="Current File" />
|
||||||
|
<option name="trackOnlyRoot" value="true" />
|
||||||
|
<option name="workingDir" value="$ProjectFileDir$" />
|
||||||
|
<envs>
|
||||||
|
<env name="GOROOT" value="$GOROOT$" />
|
||||||
|
<env name="GOPATH" value="$GOPATH$" />
|
||||||
|
<env name="PATH" value="$GoBinDirs$" />
|
||||||
|
</envs>
|
||||||
|
</TaskOptions>
|
||||||
|
<TaskOptions isEnabled="true">
|
||||||
|
<option name="arguments" value="-set_exit_status $FilePath$" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="NEVER" />
|
||||||
|
<option name="fileExtension" value="go" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="golint" />
|
||||||
|
<option name="output" value="$FilePath$" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array>
|
||||||
|
<FilterInfo>
|
||||||
|
<option name="description" value="" />
|
||||||
|
<option name="name" value="" />
|
||||||
|
<option name="regExp" value="$PROJECT_DIR$/../clean-architecture-go-v2:$LINE$:$COLUMN$: $MESSAGE$" />
|
||||||
|
</FilterInfo>
|
||||||
|
</array>
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="golint" />
|
||||||
|
<option name="runOnExternalChanges" value="false" />
|
||||||
|
<option name="scopeName" value="Current File" />
|
||||||
|
<option name="trackOnlyRoot" value="true" />
|
||||||
|
<option name="workingDir" value="$ProjectFileDir$" />
|
||||||
|
<envs>
|
||||||
|
<env name="GOROOT" value="$GOROOT$" />
|
||||||
|
<env name="GOPATH" value="$GOPATH$" />
|
||||||
|
<env name="PATH" value="$GoBinDirs$" />
|
||||||
|
</envs>
|
||||||
|
</TaskOptions>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
.PHONY: all
|
||||||
|
all: build
|
||||||
|
FORCE: ;
|
||||||
|
|
||||||
|
SHELL := env LIBRARY_ENV=$(LIBRARY_ENV) $(SHELL)
|
||||||
|
LIBRARY_ENV ?= dev
|
||||||
|
|
||||||
|
BIN_DIR = $(PWD)/bin
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf bin/*
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
build: dependencies build-api build-cmd
|
||||||
|
|
||||||
|
build-api:
|
||||||
|
go build -tags $(LIBRARY_ENV) -o ./bin/api api/main.go
|
||||||
|
|
||||||
|
build-cmd:
|
||||||
|
go build -tags $(LIBRARY_ENV) -o ./bin/search cmd/main.go
|
||||||
|
|
||||||
|
linux-binaries:
|
||||||
|
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -tags "$(LIBRARY_ENV) netgo" -installsuffix netgo -o $(BIN_DIR)/api api/main.go
|
||||||
|
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -tags "$(LIBRARY_ENV) netgo" -installsuffix netgo -o $(BIN_DIR)/search cmd/main.go
|
||||||
|
|
||||||
|
ci: dependencies test
|
||||||
|
|
||||||
|
build-mocks:
|
||||||
|
@go get github.com/golang/mock/gomock
|
||||||
|
@go install github.com/golang/mock/mockgen
|
||||||
|
@~/go/bin/mockgen -source=domain/entity/book/interface.go -destination=domain/entity/book/mock/book.go -package=mock
|
||||||
|
@~/go/bin/mockgen -source=domain/entity/user/interface.go -destination=domain/entity/user/mock/user.go -package=mock
|
||||||
|
@~/go/bin/mockgen -source=domain/loan/interface.go -destination=domain/loan/mock/loan.go -package=mock
|
||||||
|
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test -tags testing ./...
|
||||||
|
|
||||||
|
fmt: ## gofmt and goimports all go files
|
||||||
|
find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Clean Architecture in Go
|
||||||
|
|
||||||
|
[](https://travis-ci.org/eminetto/clean-architecture-go)
|
||||||
|
|
||||||
|
Clean Architecture sample
|
||||||
|
|
||||||
|
## Post
|
||||||
|
|
||||||
|
[https://medium.com/@eminetto/clean-architecture-using-golang-b63587aa5e3f](https://medium.com/@eminetto/clean-architecture-using-golang-b63587aa5e3f)
|
||||||
|
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
make
|
||||||
|
|
||||||
|
## Run tests
|
||||||
|
|
||||||
|
make test
|
||||||
|
|
||||||
|
## API requests
|
||||||
|
|
||||||
|
### Add a bookmark
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X "POST" "http://localhost:8080/v1/bookmark" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
-d $'{
|
||||||
|
"tags": [
|
||||||
|
"git",
|
||||||
|
"social"
|
||||||
|
],
|
||||||
|
"name": "Github",
|
||||||
|
"description": "Github site",
|
||||||
|
"link": "http://github.com"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
### Search a bookmark
|
||||||
|
|
||||||
|
```
|
||||||
|
curl "http://localhost:8080/v1/bookmark?name=github" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Accept: application/json'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Show all bookmarks
|
||||||
|
|
||||||
|
```
|
||||||
|
curl "http://localhost:8080/v1/bookmark" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Accept: application/json'
|
||||||
|
```
|
||||||
|
|
||||||
|
## CMD
|
||||||
|
|
||||||
|
### Search for a bookmark
|
||||||
|
|
||||||
|
```
|
||||||
|
./bin/search github
|
||||||
|
```
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/eminetto/clean-architecture-go/pkg/middleware"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/codegangsta/negroni"
|
||||||
|
"github.com/eminetto/clean-architecture-go/pkg/bookmark"
|
||||||
|
"github.com/eminetto/clean-architecture-go/pkg/entity"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
valid "github.com/asaskevich/govalidator"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
//BookmarkInput data
|
||||||
|
type BookmarkInput struct {
|
||||||
|
ID entity.ID `json:"id" valid:"type(entity.ID)"`
|
||||||
|
Name string `json:"name" valid:"stringlength(1|50),required"`
|
||||||
|
Description string `json:"description" valid:"stringlength(1|150),required"`
|
||||||
|
Link string `json:"link" valid:"url,required"`
|
||||||
|
Tags []string `json:"tags" valid:"-"`
|
||||||
|
Favorite bool `json:"favorite" valid:"required"`
|
||||||
|
CreatedAt time.Time `json:"created_at" valid:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BookmarkInput) Validate() error {
|
||||||
|
_, err := valid.ValidateStruct(*v)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func bookmarkIndex(service bookmark.UseCase) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
errorMessage := "Error reading bookmarks"
|
||||||
|
var data []*entity.Bookmark
|
||||||
|
var err error
|
||||||
|
name := r.URL.Query().Get("name")
|
||||||
|
switch {
|
||||||
|
case name == "":
|
||||||
|
data, err = service.FindAll()
|
||||||
|
default:
|
||||||
|
data, err = service.Search(name)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err != nil && err != entity.ErrNotFound {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(errorMessage))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if data == nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(errorMessage))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(errorMessage))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func bookmarkAdd(service bookmark.UseCase) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
errorMessage := "Error adding bookmark"
|
||||||
|
i := r.Context().Value("InputParam").(*BookmarkInput)
|
||||||
|
b := &entity.Bookmark{
|
||||||
|
Name: i.Name,
|
||||||
|
Description: i.Description,
|
||||||
|
Link: i.Link,
|
||||||
|
Tags: i.Tags,
|
||||||
|
Favorite: i.Favorite,
|
||||||
|
}
|
||||||
|
_, err := service.Store(b)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(errorMessage))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
if err := json.NewEncoder(w).Encode(b); err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(errorMessage))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func bookmarkFind(service bookmark.UseCase) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
errorMessage := "Error reading bookmark"
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id := vars["id"]
|
||||||
|
data, err := service.Find(entity.StringToID(id))
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err != nil && err != entity.ErrNotFound {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(errorMessage))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if data == nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(errorMessage))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(errorMessage))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func bookmarkDelete(service bookmark.UseCase) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
errorMessage := "Error removing bookmark"
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id := vars["id"]
|
||||||
|
err := service.Delete(entity.StringToID(id))
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(errorMessage))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//MakeBookmarkHandlers make url handlers
|
||||||
|
func MakeBookmarkHandlers(r *mux.Router, n negroni.Negroni, service bookmark.UseCase) {
|
||||||
|
r.Handle("/v1/bookmark", n.With(
|
||||||
|
negroni.Wrap(bookmarkIndex(service)),
|
||||||
|
)).Methods("GET", "OPTIONS").Name("bookmarkIndex")
|
||||||
|
|
||||||
|
r.Handle("/v1/bookmark", n.With(
|
||||||
|
negroni.HandlerFunc(middleware.Validate(&BookmarkInput{})),
|
||||||
|
negroni.Wrap(bookmarkAdd(service)),
|
||||||
|
)).Methods("POST", "OPTIONS").Name("bookmarkAdd")
|
||||||
|
|
||||||
|
r.Handle("/v1/bookmark/{id}", n.With(
|
||||||
|
negroni.Wrap(bookmarkFind(service)),
|
||||||
|
)).Methods("GET", "OPTIONS").Name("bookmarkFind")
|
||||||
|
|
||||||
|
r.Handle("/v1/bookmark/{id}", n.With(
|
||||||
|
negroni.Wrap(bookmarkDelete(service)),
|
||||||
|
)).Methods("DELETE", "OPTIONS").Name("bookmarkDelete")
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/codegangsta/negroni"
|
||||||
|
"github.com/eminetto/clean-architecture-go/pkg/bookmark/mock"
|
||||||
|
"github.com/eminetto/clean-architecture-go/pkg/entity"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBookmarkIndex(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
service := mock.NewMockUseCase(controller)
|
||||||
|
r := mux.NewRouter()
|
||||||
|
n := negroni.New()
|
||||||
|
MakeBookmarkHandlers(r, *n, service)
|
||||||
|
path, err := r.GetRoute("bookmarkIndex").GetPathTemplate()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "/v1/bookmark", path)
|
||||||
|
b := &entity.Bookmark{
|
||||||
|
Name: "Elton Minetto",
|
||||||
|
Description: "Minetto's page",
|
||||||
|
Link: "http://www.eltonminetto.net",
|
||||||
|
Tags: []string{"golang", "php", "linux", "mac"},
|
||||||
|
Favorite: true,
|
||||||
|
}
|
||||||
|
service.EXPECT().
|
||||||
|
FindAll().
|
||||||
|
Return([]*entity.Bookmark{b}, nil)
|
||||||
|
ts := httptest.NewServer(bookmarkIndex(service))
|
||||||
|
defer ts.Close()
|
||||||
|
res, err := http.Get(ts.URL)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBookmarkIndexNotFound(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
service := mock.NewMockUseCase(controller)
|
||||||
|
ts := httptest.NewServer(bookmarkIndex(service))
|
||||||
|
defer ts.Close()
|
||||||
|
service.EXPECT().
|
||||||
|
Search("github").
|
||||||
|
Return(nil, entity.ErrNotFound)
|
||||||
|
res, err := http.Get(ts.URL + "?name=github")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBookmarkSearch(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
service := mock.NewMockUseCase(controller)
|
||||||
|
b := &entity.Bookmark{
|
||||||
|
Name: "Elton Minetto",
|
||||||
|
Description: "Minetto's page",
|
||||||
|
Link: "http://www.eltonminetto.net",
|
||||||
|
Tags: []string{"golang", "php", "linux", "mac"},
|
||||||
|
Favorite: true,
|
||||||
|
}
|
||||||
|
service.EXPECT().
|
||||||
|
Search("minetto").
|
||||||
|
Return([]*entity.Bookmark{b}, nil)
|
||||||
|
ts := httptest.NewServer(bookmarkIndex(service))
|
||||||
|
defer ts.Close()
|
||||||
|
res, err := http.Get(ts.URL + "?name=minetto")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBookmarkAdd(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
service := mock.NewMockUseCase(controller)
|
||||||
|
r := mux.NewRouter()
|
||||||
|
n := negroni.New()
|
||||||
|
MakeBookmarkHandlers(r, *n, service)
|
||||||
|
path, err := r.GetRoute("bookmarkAdd").GetPathTemplate()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "/v1/bookmark", path)
|
||||||
|
|
||||||
|
service.EXPECT().
|
||||||
|
Store(gomock.Any()).
|
||||||
|
Return(entity.NewID(), nil)
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
defer ts.Close()
|
||||||
|
payload := fmt.Sprintf(`{
|
||||||
|
"name": "Github",
|
||||||
|
"description": "Github site",
|
||||||
|
"link": "http://github.com",
|
||||||
|
"tags": [
|
||||||
|
"git",
|
||||||
|
"social"
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
resp, _ := http.Post(ts.URL+"/v1/bookmark", "application/json", strings.NewReader(payload))
|
||||||
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||||
|
|
||||||
|
var b *entity.Bookmark
|
||||||
|
json.NewDecoder(resp.Body).Decode(&b)
|
||||||
|
assert.Equal(t, "http://github.com", b.Link)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBookmarkFind(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
service := mock.NewMockUseCase(controller)
|
||||||
|
r := mux.NewRouter()
|
||||||
|
n := negroni.New()
|
||||||
|
MakeBookmarkHandlers(r, *n, service)
|
||||||
|
path, err := r.GetRoute("bookmarkFind").GetPathTemplate()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "/v1/bookmark/{id}", path)
|
||||||
|
b := &entity.Bookmark{
|
||||||
|
ID: entity.NewID(),
|
||||||
|
Name: "Elton Minetto",
|
||||||
|
Description: "Minetto's page",
|
||||||
|
Link: "http://www.eltonminetto.net",
|
||||||
|
Tags: []string{"golang", "php", "linux", "mac"},
|
||||||
|
Favorite: true,
|
||||||
|
}
|
||||||
|
service.EXPECT().
|
||||||
|
Find(b.ID).
|
||||||
|
Return(b, nil)
|
||||||
|
handler := bookmarkFind(service)
|
||||||
|
r.Handle("/v1/bookmark/{id}", handler)
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
defer ts.Close()
|
||||||
|
res, err := http.Get(ts.URL + "/v1/bookmark/" + b.ID.String())
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
var d *entity.Bookmark
|
||||||
|
json.NewDecoder(res.Body).Decode(&d)
|
||||||
|
assert.NotNil(t, d)
|
||||||
|
assert.Equal(t, b.ID, d.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBookmarkRemove(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
service := mock.NewMockUseCase(controller)
|
||||||
|
r := mux.NewRouter()
|
||||||
|
n := negroni.New()
|
||||||
|
MakeBookmarkHandlers(r, *n, service)
|
||||||
|
path, err := r.GetRoute("bookmarkDelete").GetPathTemplate()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "/v1/bookmark/{id}", path)
|
||||||
|
b := &entity.Bookmark{
|
||||||
|
ID: entity.NewID(),
|
||||||
|
Name: "Elton Minetto",
|
||||||
|
Description: "Minetto's page",
|
||||||
|
Link: "http://www.eltonminetto.net",
|
||||||
|
Tags: []string{"golang", "php", "linux", "mac"},
|
||||||
|
Favorite: false,
|
||||||
|
}
|
||||||
|
service.EXPECT().Delete(b.ID).Return(nil)
|
||||||
|
handler := bookmarkDelete(service)
|
||||||
|
req, _ := http.NewRequest("DELETE", "/v1/bookmark/"+b.ID.String(), nil)
|
||||||
|
r.Handle("/v1/bookmark/{id}", handler).Methods("DELETE", "OPTIONS")
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(rr, req)
|
||||||
|
assert.Equal(t, http.StatusOK, rr.Code)
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/codegangsta/negroni"
|
||||||
|
"github.com/eminetto/clean-architecture-go/api/handler"
|
||||||
|
"github.com/eminetto/clean-architecture-go/config"
|
||||||
|
"github.com/eminetto/clean-architecture-go/pkg/bookmark"
|
||||||
|
"github.com/eminetto/clean-architecture-go/pkg/middleware"
|
||||||
|
"github.com/eminetto/clean-architecture-go/pkg/metric"
|
||||||
|
"github.com/gorilla/context"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/juju/mgosession"
|
||||||
|
mgo "gopkg.in/mgo.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
session, err := mgo.Dial(config.MONGODB_HOST)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
mPool := mgosession.NewPool(nil, session, config.MONGODB_CONNECTION_POOL)
|
||||||
|
defer mPool.Close()
|
||||||
|
|
||||||
|
bookmarkRepo := bookmark.NewMongoRepository(mPool, config.MONGODB_DATABASE)
|
||||||
|
bookmarkService := bookmark.NewService(bookmarkRepo)
|
||||||
|
|
||||||
|
metricService, err := metric.NewPrometheusService()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
//handlers
|
||||||
|
n := negroni.New(
|
||||||
|
negroni.HandlerFunc(middleware.Cors),
|
||||||
|
negroni.HandlerFunc(middleware.Metrics(metricService)),
|
||||||
|
negroni.NewLogger(),
|
||||||
|
)
|
||||||
|
//bookmark
|
||||||
|
handler.MakeBookmarkHandlers(r, *n, bookmarkService)
|
||||||
|
|
||||||
|
http.Handle("/", r)
|
||||||
|
http.Handle("/metrics", promhttp.Handler())
|
||||||
|
r.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
logger := log.New(os.Stderr, "logger: ", log.Lshortfile)
|
||||||
|
srv := &http.Server{
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
Addr: ":" + strconv.Itoa(config.API_PORT),
|
||||||
|
Handler: context.ClearHandler(http.DefaultServeMux),
|
||||||
|
ErrorLog: logger,
|
||||||
|
}
|
||||||
|
err = srv.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Cors adiciona os headers para suportar o CORS nos navegadores
|
||||||
|
func Cors(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type")
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if r.Method == "OPTIONS" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go/pkg/metric"
|
||||||
|
|
||||||
|
"github.com/codegangsta/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Metrics to prometheus
|
||||||
|
func Metrics(mService metric.UseCase) negroni.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
appMetric := metric.NewHTTP(r.URL.Path, r.Method)
|
||||||
|
appMetric.Started()
|
||||||
|
next(w, r)
|
||||||
|
res := w.(negroni.ResponseWriter)
|
||||||
|
appMetric.Finished()
|
||||||
|
appMetric.StatusCode = strconv.Itoa(res.Status())
|
||||||
|
mService.SaveHTTP(appMetric)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/eminetto/clean-architecture-go/pkg/entity"
|
||||||
|
"net/http"
|
||||||
|
"github.com/codegangsta/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Validate
|
||||||
|
func Validate(e entity.Validable) negroni.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&e)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = e.Validate()
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := context.WithValue(r.Context(), "InputParam", e)
|
||||||
|
next(w, r.WithContext(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
BIN
Binary file not shown.
+65
@@ -0,0 +1,65 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity/user"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity/book"
|
||||||
|
)
|
||||||
|
|
||||||
|
//func handleParams() (string, error) {
|
||||||
|
// if len(os.Args) < 2 {
|
||||||
|
// return "", errors.New("Invalid query")
|
||||||
|
// }
|
||||||
|
// return os.Args[1], nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u := &user.User{
|
||||||
|
ID: entity.NewID(),
|
||||||
|
FirstName: "Elton",
|
||||||
|
}
|
||||||
|
b := &book.Book{
|
||||||
|
ID: entity.NewID(),
|
||||||
|
Title:"Book",
|
||||||
|
}
|
||||||
|
fmt.Println(u)
|
||||||
|
fmt.Println(b)
|
||||||
|
//metricService, err := metric.NewPrometheusService()
|
||||||
|
//if err != nil {
|
||||||
|
// log.Fatal(err.Error())
|
||||||
|
//}
|
||||||
|
//appMetric := metric.NewCLI("search")
|
||||||
|
//appMetric.Started()
|
||||||
|
//query, err := handleParams()
|
||||||
|
//if err != nil {
|
||||||
|
// log.Fatal(err.Error())
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//session, err := mgo.Dial(config.MONGODB_HOST)
|
||||||
|
//if err != nil {
|
||||||
|
// log.Fatal(err.Error())
|
||||||
|
//}
|
||||||
|
//defer session.Close()
|
||||||
|
//
|
||||||
|
//mPool := mgosession.NewPool(nil, session, config.MONGODB_CONNECTION_POOL)
|
||||||
|
//defer mPool.Close()
|
||||||
|
//
|
||||||
|
//bookmarkRepo := bookmark.NewMongoRepository(mPool, config.MONGODB_DATABASE)
|
||||||
|
//bookmarkService := bookmark.NewService(bookmarkRepo)
|
||||||
|
//all, err := bookmarkService.Search(query)
|
||||||
|
//if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
//}
|
||||||
|
//if len(all) == 0 {
|
||||||
|
// log.Fatal(entity.ErrNotFound.Error())
|
||||||
|
//}
|
||||||
|
//for _, j := range all {
|
||||||
|
// fmt.Printf("%s %s %v \n", j.Name, j.Link, j.Tags)
|
||||||
|
//}
|
||||||
|
//appMetric.Finished()
|
||||||
|
//err = metricService.SaveCLI(appMetric)
|
||||||
|
//if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
//}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWithArgs(t *testing.T) {
|
||||||
|
os.Args = []string{"search", "github"}
|
||||||
|
query, err := handleParams()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "github", query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithoutArgs(t *testing.T) {
|
||||||
|
os.Args = []string{"search"}
|
||||||
|
query, err := handleParams()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "Invalid query", err.Error())
|
||||||
|
assert.Equal(t, "", query)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// +build dev
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
const (
|
||||||
|
MONGODB_HOST = "mongodb://127.0.0.1:27017"
|
||||||
|
MONGODB_DATABASE = "bookmark"
|
||||||
|
MONGODB_CONNECTION_POOL = 5
|
||||||
|
API_PORT = 8080
|
||||||
|
PROMETHEUS_PUSHGATEWAY = "http://localhost:9091/"
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// +build prod
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
const (
|
||||||
|
MONGODB_HOST = "mongodb://127.0.0.1:27017"
|
||||||
|
MONGODB_DATABASE = "bookmark"
|
||||||
|
MONGODB_CONNECTION_POOL = 50
|
||||||
|
API_PORT = 8080
|
||||||
|
PROMETHEUS_PUSHGATEWAY = "http://localhost:9091/"
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// +build staging
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
const (
|
||||||
|
MONGODB_HOST = "mongodb://127.0.0.1:27017"
|
||||||
|
MONGODB_DATABASE = "bookmark"
|
||||||
|
MONGODB_CONNECTION_POOL = 5
|
||||||
|
API_PORT = 8080
|
||||||
|
PROMETHEUS_PUSHGATEWAY = "http://localhost:9091/"
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// +build testing
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
const (
|
||||||
|
MONGODB_HOST = "mongodb://127.0.0.1:27017"
|
||||||
|
MONGODB_DATABASE = "bookmark"
|
||||||
|
MONGODB_CONNECTION_POOL = 5
|
||||||
|
API_PORT = 8080
|
||||||
|
PROMETHEUS_PUSHGATEWAY = "http://localhost:9091/"
|
||||||
|
)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
mongodb:
|
||||||
|
image: mongo
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
container_name: bookmark-mongodb
|
||||||
|
network_mode: "bridge"
|
||||||
|
node:
|
||||||
|
image: node:8-alpine
|
||||||
|
network_mode: "bridge"
|
||||||
|
volumes:
|
||||||
|
- ./web:/web
|
||||||
|
- /tmp:/tmp
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
container_name: bookmark-grafana
|
||||||
|
network_mode: "bridge"
|
||||||
|
depends_on:
|
||||||
|
- prometheus
|
||||||
|
- prometheus-pushgateway
|
||||||
|
prometheus:
|
||||||
|
image: prom/prometheus
|
||||||
|
ports:
|
||||||
|
- 9090:9090
|
||||||
|
command:
|
||||||
|
- --config.file=/etc/prometheus/prometheus.yml
|
||||||
|
volumes:
|
||||||
|
- ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||||
|
container_name: bookmark-prometheus
|
||||||
|
network_mode: "bridge"
|
||||||
|
prometheus-pushgateway:
|
||||||
|
image: prom/pushgateway
|
||||||
|
container_name: bookmark-pushgateway
|
||||||
|
expose:
|
||||||
|
- 9091
|
||||||
|
ports:
|
||||||
|
- "9091:9091"
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package book
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Book data
|
||||||
|
type Book struct {
|
||||||
|
ID entity.ID
|
||||||
|
Title string
|
||||||
|
Author string
|
||||||
|
Pages int
|
||||||
|
Quantity int
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package book
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFixtureBook() *Book {
|
||||||
|
return &Book{
|
||||||
|
ID: entity.NewID(),
|
||||||
|
Title: "I Am Ozzy",
|
||||||
|
Author: "Ozzy Osbourne",
|
||||||
|
Pages: 294,
|
||||||
|
Quantity: 1,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package book
|
||||||
|
|
||||||
|
import "github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
|
||||||
|
//Reader interface
|
||||||
|
type Reader interface {
|
||||||
|
Get(id entity.ID) (*Book, error)
|
||||||
|
Search(query string) ([]*Book, error)
|
||||||
|
List() ([]*Book, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Writer book writer
|
||||||
|
type Writer interface {
|
||||||
|
Create(e *Book) (entity.ID, error)
|
||||||
|
Update(e *Book) error
|
||||||
|
Delete(id entity.ID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
//Repository repository interface
|
||||||
|
type Repository interface {
|
||||||
|
Reader
|
||||||
|
Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
//UseCase use case interface
|
||||||
|
type UseCase interface {
|
||||||
|
Reader
|
||||||
|
Writer
|
||||||
|
}
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: domain/entity/book/interface.go
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
book "github.com/eminetto/clean-architecture-go-v2/domain/entity/book"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
reflect "reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockReader is a mock of Reader interface
|
||||||
|
type MockReader struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockReaderMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockReaderMockRecorder is the mock recorder for MockReader
|
||||||
|
type MockReaderMockRecorder struct {
|
||||||
|
mock *MockReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockReader creates a new mock instance
|
||||||
|
func NewMockReader(ctrl *gomock.Controller) *MockReader {
|
||||||
|
mock := &MockReader{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockReaderMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockReader) EXPECT() *MockReaderMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mocks base method
|
||||||
|
func (m *MockReader) Get(id entity.ID) (*book.Book, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Get", id)
|
||||||
|
ret0, _ := ret[0].(*book.Book)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get indicates an expected call of Get
|
||||||
|
func (mr *MockReaderMockRecorder) Get(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockReader)(nil).Get), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search mocks base method
|
||||||
|
func (m *MockReader) Search(query string) ([]*book.Book, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Search", query)
|
||||||
|
ret0, _ := ret[0].([]*book.Book)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search indicates an expected call of Search
|
||||||
|
func (mr *MockReaderMockRecorder) Search(query interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockReader)(nil).Search), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List mocks base method
|
||||||
|
func (m *MockReader) List() ([]*book.Book, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "List")
|
||||||
|
ret0, _ := ret[0].([]*book.Book)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// List indicates an expected call of List
|
||||||
|
func (mr *MockReaderMockRecorder) List() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockReader)(nil).List))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockWriter is a mock of Writer interface
|
||||||
|
type MockWriter struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockWriterMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockWriterMockRecorder is the mock recorder for MockWriter
|
||||||
|
type MockWriterMockRecorder struct {
|
||||||
|
mock *MockWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockWriter creates a new mock instance
|
||||||
|
func NewMockWriter(ctrl *gomock.Controller) *MockWriter {
|
||||||
|
mock := &MockWriter{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockWriterMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockWriter) EXPECT() *MockWriterMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mocks base method
|
||||||
|
func (m *MockWriter) Create(e *book.Book) (entity.ID, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Create", e)
|
||||||
|
ret0, _ := ret[0].(entity.ID)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indicates an expected call of Create
|
||||||
|
func (mr *MockWriterMockRecorder) Create(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockWriter)(nil).Create), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method
|
||||||
|
func (m *MockWriter) Update(e *book.Book) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", e)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update
|
||||||
|
func (mr *MockWriterMockRecorder) Update(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockWriter)(nil).Update), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method
|
||||||
|
func (m *MockWriter) Delete(id entity.ID) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Delete", id)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete indicates an expected call of Delete
|
||||||
|
func (mr *MockWriterMockRecorder) Delete(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockWriter)(nil).Delete), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockRepository is a mock of Repository interface
|
||||||
|
type MockRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockRepositoryMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockRepositoryMockRecorder is the mock recorder for MockRepository
|
||||||
|
type MockRepositoryMockRecorder struct {
|
||||||
|
mock *MockRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockRepository creates a new mock instance
|
||||||
|
func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
|
||||||
|
mock := &MockRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mocks base method
|
||||||
|
func (m *MockRepository) Get(id entity.ID) (*book.Book, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Get", id)
|
||||||
|
ret0, _ := ret[0].(*book.Book)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get indicates an expected call of Get
|
||||||
|
func (mr *MockRepositoryMockRecorder) Get(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRepository)(nil).Get), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search mocks base method
|
||||||
|
func (m *MockRepository) Search(query string) ([]*book.Book, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Search", query)
|
||||||
|
ret0, _ := ret[0].([]*book.Book)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search indicates an expected call of Search
|
||||||
|
func (mr *MockRepositoryMockRecorder) Search(query interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockRepository)(nil).Search), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List mocks base method
|
||||||
|
func (m *MockRepository) List() ([]*book.Book, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "List")
|
||||||
|
ret0, _ := ret[0].([]*book.Book)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// List indicates an expected call of List
|
||||||
|
func (mr *MockRepositoryMockRecorder) List() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockRepository)(nil).List))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mocks base method
|
||||||
|
func (m *MockRepository) Create(e *book.Book) (entity.ID, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Create", e)
|
||||||
|
ret0, _ := ret[0].(entity.ID)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indicates an expected call of Create
|
||||||
|
func (mr *MockRepositoryMockRecorder) Create(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRepository)(nil).Create), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method
|
||||||
|
func (m *MockRepository) Update(e *book.Book) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", e)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update
|
||||||
|
func (mr *MockRepositoryMockRecorder) Update(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRepository)(nil).Update), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method
|
||||||
|
func (m *MockRepository) Delete(id entity.ID) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Delete", id)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete indicates an expected call of Delete
|
||||||
|
func (mr *MockRepositoryMockRecorder) Delete(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockUseCase is a mock of UseCase interface
|
||||||
|
type MockUseCase struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockUseCaseMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockUseCaseMockRecorder is the mock recorder for MockUseCase
|
||||||
|
type MockUseCaseMockRecorder struct {
|
||||||
|
mock *MockUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockUseCase creates a new mock instance
|
||||||
|
func NewMockUseCase(ctrl *gomock.Controller) *MockUseCase {
|
||||||
|
mock := &MockUseCase{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockUseCaseMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockUseCase) EXPECT() *MockUseCaseMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mocks base method
|
||||||
|
func (m *MockUseCase) Get(id entity.ID) (*book.Book, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Get", id)
|
||||||
|
ret0, _ := ret[0].(*book.Book)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get indicates an expected call of Get
|
||||||
|
func (mr *MockUseCaseMockRecorder) Get(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockUseCase)(nil).Get), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search mocks base method
|
||||||
|
func (m *MockUseCase) Search(query string) ([]*book.Book, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Search", query)
|
||||||
|
ret0, _ := ret[0].([]*book.Book)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search indicates an expected call of Search
|
||||||
|
func (mr *MockUseCaseMockRecorder) Search(query interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockUseCase)(nil).Search), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List mocks base method
|
||||||
|
func (m *MockUseCase) List() ([]*book.Book, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "List")
|
||||||
|
ret0, _ := ret[0].([]*book.Book)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// List indicates an expected call of List
|
||||||
|
func (mr *MockUseCaseMockRecorder) List() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockUseCase)(nil).List))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mocks base method
|
||||||
|
func (m *MockUseCase) Create(e *book.Book) (entity.ID, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Create", e)
|
||||||
|
ret0, _ := ret[0].(entity.ID)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indicates an expected call of Create
|
||||||
|
func (mr *MockUseCaseMockRecorder) Create(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockUseCase)(nil).Create), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method
|
||||||
|
func (m *MockUseCase) Update(e *book.Book) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", e)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update
|
||||||
|
func (mr *MockUseCaseMockRecorder) Update(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUseCase)(nil).Update), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method
|
||||||
|
func (m *MockUseCase) Delete(id entity.ID) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Delete", id)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete indicates an expected call of Delete
|
||||||
|
func (mr *MockUseCaseMockRecorder) Delete(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockUseCase)(nil).Delete), id)
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package book
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
//IRepo in memory repo
|
||||||
|
type IRepo struct {
|
||||||
|
m map[entity.ID]*Book
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewInmemRepository create new repository
|
||||||
|
func NewInmemRepository() *IRepo {
|
||||||
|
var m = map[entity.ID]*Book{}
|
||||||
|
return &IRepo{
|
||||||
|
m: m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create a book
|
||||||
|
func (r *IRepo) Create(e *Book) (entity.ID, error) {
|
||||||
|
r.m[e.ID] = e
|
||||||
|
return e.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get a book
|
||||||
|
func (r *IRepo) Get(id entity.ID) (*Book, error) {
|
||||||
|
if r.m[id] == nil {
|
||||||
|
return nil, domain.ErrNotFound
|
||||||
|
}
|
||||||
|
return r.m[id], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update a book
|
||||||
|
func (r *IRepo) Update(e *Book) error {
|
||||||
|
_, err := r.Get(e.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.m[e.ID] = e
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Search books
|
||||||
|
func (r *IRepo) Search(query string) ([]*Book, error) {
|
||||||
|
var d []*Book
|
||||||
|
for _, j := range r.m {
|
||||||
|
if strings.Contains(strings.ToLower(j.Title), query) {
|
||||||
|
d = append(d, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(d) == 0 {
|
||||||
|
return nil, domain.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//List books
|
||||||
|
func (r *IRepo) List() ([]*Book, error) {
|
||||||
|
var d []*Book
|
||||||
|
for _, j := range r.m {
|
||||||
|
d = append(d, j)
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete a book
|
||||||
|
func (r *IRepo) Delete(id entity.ID) error {
|
||||||
|
if r.m[id] == nil {
|
||||||
|
return domain.ErrNotFound
|
||||||
|
}
|
||||||
|
r.m[id] = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package book
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Service service interface
|
||||||
|
type Service struct {
|
||||||
|
repo Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewService create new use case
|
||||||
|
func NewService(r Repository) *Service {
|
||||||
|
return &Service{
|
||||||
|
repo: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create a book
|
||||||
|
func (s *Service) Create(e *Book) (entity.ID, error) {
|
||||||
|
e.ID = entity.NewID()
|
||||||
|
e.CreatedAt = time.Now()
|
||||||
|
return s.repo.Create(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get a book
|
||||||
|
func (s *Service) Get(id entity.ID) (*Book, error) {
|
||||||
|
return s.repo.Get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Search books
|
||||||
|
func (s *Service) Search(query string) ([]*Book, error) {
|
||||||
|
return s.repo.Search(strings.ToLower(query))
|
||||||
|
}
|
||||||
|
|
||||||
|
//List books
|
||||||
|
func (s *Service) List() ([]*Book, error) {
|
||||||
|
return s.repo.List()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete a book
|
||||||
|
func (s *Service) Delete(id entity.ID) error {
|
||||||
|
_, err := s.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.repo.Delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update a book
|
||||||
|
func (s *Service) Update(e *Book) error {
|
||||||
|
return s.repo.Update(e)
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package book
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Create(t *testing.T) {
|
||||||
|
repo := NewInmemRepository()
|
||||||
|
service := NewService(repo)
|
||||||
|
u := NewFixtureBook()
|
||||||
|
id, err := service.Create(u)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, u.ID, id)
|
||||||
|
assert.False(t, u.CreatedAt.IsZero())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_SearchAndFind(t *testing.T) {
|
||||||
|
repo := NewInmemRepository()
|
||||||
|
service := NewService(repo)
|
||||||
|
u1 := NewFixtureBook()
|
||||||
|
u2 := NewFixtureBook()
|
||||||
|
u2.Title = "Lemmy: Biography"
|
||||||
|
|
||||||
|
uID, _ := service.Create(u1)
|
||||||
|
_, _ = service.Create(u2)
|
||||||
|
|
||||||
|
t.Run("search", func(t *testing.T) {
|
||||||
|
c, err := service.Search("ozzy")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 1, len(c))
|
||||||
|
assert.Equal(t, "I Am Ozzy", c[0].Title)
|
||||||
|
|
||||||
|
c, err = service.Search("dio")
|
||||||
|
assert.Equal(t, domain.ErrNotFound, err)
|
||||||
|
assert.Nil(t, c)
|
||||||
|
})
|
||||||
|
t.Run("list all", func(t *testing.T) {
|
||||||
|
all, err := service.List()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 2, len(all))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get", func(t *testing.T) {
|
||||||
|
saved, err := service.Get(uID)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, u1.Title, saved.Title)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Update(t *testing.T) {
|
||||||
|
repo := NewInmemRepository()
|
||||||
|
service := NewService(repo)
|
||||||
|
u := NewFixtureBook()
|
||||||
|
id, err := service.Create(u)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
saved, _ := service.Get(id)
|
||||||
|
saved.Title = "Lemmy: Biography"
|
||||||
|
assert.Nil(t, service.Update(saved))
|
||||||
|
updated, err := service.Get(id)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "Lemmy: Biography", updated.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
repo := NewInmemRepository()
|
||||||
|
service := NewService(repo)
|
||||||
|
u1 := NewFixtureBook()
|
||||||
|
u2 := NewFixtureBook()
|
||||||
|
u2ID, _ := service.Create(u2)
|
||||||
|
|
||||||
|
err := service.Delete(u1.ID)
|
||||||
|
assert.Equal(t, domain.ErrNotFound, err)
|
||||||
|
|
||||||
|
err = service.Delete(u2ID)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
_, err = service.Get(u2ID)
|
||||||
|
assert.Equal(t, domain.ErrNotFound, err)
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
|
type ID = uuid.UUID
|
||||||
|
|
||||||
|
func NewID() ID {
|
||||||
|
return ID(uuid.New())
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToID(s string) (ID, error) {
|
||||||
|
id, err := uuid.Parse(s)
|
||||||
|
return ID(id), err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFixtureUser() *User {
|
||||||
|
return &User{
|
||||||
|
ID: entity.NewID(),
|
||||||
|
Email:"ozzy@metalgods.net",
|
||||||
|
FirstName:"Ozzy",
|
||||||
|
LastName: "Osbourne",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import "github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
|
||||||
|
//Reader interface
|
||||||
|
type Reader interface {
|
||||||
|
Get(id entity.ID) (*User, error)
|
||||||
|
Search(query string) ([]*User, error)
|
||||||
|
List() ([]*User, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Writer user writer
|
||||||
|
type Writer interface {
|
||||||
|
Create(e *User) (entity.ID, error)
|
||||||
|
Update(e *User) error
|
||||||
|
Delete(id entity.ID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
//Repository repository interface
|
||||||
|
type Repository interface {
|
||||||
|
Reader
|
||||||
|
Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
//UseCase use case interface
|
||||||
|
type UseCase interface {
|
||||||
|
Reader
|
||||||
|
Writer
|
||||||
|
}
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: domain/entity/user/interface.go
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
user "github.com/eminetto/clean-architecture-go-v2/domain/entity/user"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
reflect "reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockReader is a mock of Reader interface
|
||||||
|
type MockReader struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockReaderMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockReaderMockRecorder is the mock recorder for MockReader
|
||||||
|
type MockReaderMockRecorder struct {
|
||||||
|
mock *MockReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockReader creates a new mock instance
|
||||||
|
func NewMockReader(ctrl *gomock.Controller) *MockReader {
|
||||||
|
mock := &MockReader{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockReaderMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockReader) EXPECT() *MockReaderMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mocks base method
|
||||||
|
func (m *MockReader) Get(id entity.ID) (*user.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Get", id)
|
||||||
|
ret0, _ := ret[0].(*user.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get indicates an expected call of Get
|
||||||
|
func (mr *MockReaderMockRecorder) Get(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockReader)(nil).Get), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search mocks base method
|
||||||
|
func (m *MockReader) Search(query string) ([]*user.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Search", query)
|
||||||
|
ret0, _ := ret[0].([]*user.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search indicates an expected call of Search
|
||||||
|
func (mr *MockReaderMockRecorder) Search(query interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockReader)(nil).Search), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List mocks base method
|
||||||
|
func (m *MockReader) List() ([]*user.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "List")
|
||||||
|
ret0, _ := ret[0].([]*user.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// List indicates an expected call of List
|
||||||
|
func (mr *MockReaderMockRecorder) List() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockReader)(nil).List))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockWriter is a mock of Writer interface
|
||||||
|
type MockWriter struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockWriterMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockWriterMockRecorder is the mock recorder for MockWriter
|
||||||
|
type MockWriterMockRecorder struct {
|
||||||
|
mock *MockWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockWriter creates a new mock instance
|
||||||
|
func NewMockWriter(ctrl *gomock.Controller) *MockWriter {
|
||||||
|
mock := &MockWriter{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockWriterMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockWriter) EXPECT() *MockWriterMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mocks base method
|
||||||
|
func (m *MockWriter) Create(e *user.User) (entity.ID, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Create", e)
|
||||||
|
ret0, _ := ret[0].(entity.ID)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indicates an expected call of Create
|
||||||
|
func (mr *MockWriterMockRecorder) Create(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockWriter)(nil).Create), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method
|
||||||
|
func (m *MockWriter) Update(e *user.User) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", e)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update
|
||||||
|
func (mr *MockWriterMockRecorder) Update(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockWriter)(nil).Update), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method
|
||||||
|
func (m *MockWriter) Delete(id entity.ID) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Delete", id)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete indicates an expected call of Delete
|
||||||
|
func (mr *MockWriterMockRecorder) Delete(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockWriter)(nil).Delete), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockRepository is a mock of Repository interface
|
||||||
|
type MockRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockRepositoryMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockRepositoryMockRecorder is the mock recorder for MockRepository
|
||||||
|
type MockRepositoryMockRecorder struct {
|
||||||
|
mock *MockRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockRepository creates a new mock instance
|
||||||
|
func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
|
||||||
|
mock := &MockRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mocks base method
|
||||||
|
func (m *MockRepository) Get(id entity.ID) (*user.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Get", id)
|
||||||
|
ret0, _ := ret[0].(*user.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get indicates an expected call of Get
|
||||||
|
func (mr *MockRepositoryMockRecorder) Get(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRepository)(nil).Get), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search mocks base method
|
||||||
|
func (m *MockRepository) Search(query string) ([]*user.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Search", query)
|
||||||
|
ret0, _ := ret[0].([]*user.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search indicates an expected call of Search
|
||||||
|
func (mr *MockRepositoryMockRecorder) Search(query interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockRepository)(nil).Search), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List mocks base method
|
||||||
|
func (m *MockRepository) List() ([]*user.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "List")
|
||||||
|
ret0, _ := ret[0].([]*user.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// List indicates an expected call of List
|
||||||
|
func (mr *MockRepositoryMockRecorder) List() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockRepository)(nil).List))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mocks base method
|
||||||
|
func (m *MockRepository) Create(e *user.User) (entity.ID, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Create", e)
|
||||||
|
ret0, _ := ret[0].(entity.ID)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indicates an expected call of Create
|
||||||
|
func (mr *MockRepositoryMockRecorder) Create(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRepository)(nil).Create), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method
|
||||||
|
func (m *MockRepository) Update(e *user.User) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", e)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update
|
||||||
|
func (mr *MockRepositoryMockRecorder) Update(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRepository)(nil).Update), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method
|
||||||
|
func (m *MockRepository) Delete(id entity.ID) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Delete", id)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete indicates an expected call of Delete
|
||||||
|
func (mr *MockRepositoryMockRecorder) Delete(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockUseCase is a mock of UseCase interface
|
||||||
|
type MockUseCase struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockUseCaseMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockUseCaseMockRecorder is the mock recorder for MockUseCase
|
||||||
|
type MockUseCaseMockRecorder struct {
|
||||||
|
mock *MockUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockUseCase creates a new mock instance
|
||||||
|
func NewMockUseCase(ctrl *gomock.Controller) *MockUseCase {
|
||||||
|
mock := &MockUseCase{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockUseCaseMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockUseCase) EXPECT() *MockUseCaseMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mocks base method
|
||||||
|
func (m *MockUseCase) Get(id entity.ID) (*user.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Get", id)
|
||||||
|
ret0, _ := ret[0].(*user.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get indicates an expected call of Get
|
||||||
|
func (mr *MockUseCaseMockRecorder) Get(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockUseCase)(nil).Get), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search mocks base method
|
||||||
|
func (m *MockUseCase) Search(query string) ([]*user.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Search", query)
|
||||||
|
ret0, _ := ret[0].([]*user.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search indicates an expected call of Search
|
||||||
|
func (mr *MockUseCaseMockRecorder) Search(query interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockUseCase)(nil).Search), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List mocks base method
|
||||||
|
func (m *MockUseCase) List() ([]*user.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "List")
|
||||||
|
ret0, _ := ret[0].([]*user.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// List indicates an expected call of List
|
||||||
|
func (mr *MockUseCaseMockRecorder) List() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockUseCase)(nil).List))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mocks base method
|
||||||
|
func (m *MockUseCase) Create(e *user.User) (entity.ID, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Create", e)
|
||||||
|
ret0, _ := ret[0].(entity.ID)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indicates an expected call of Create
|
||||||
|
func (mr *MockUseCaseMockRecorder) Create(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockUseCase)(nil).Create), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method
|
||||||
|
func (m *MockUseCase) Update(e *user.User) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", e)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update
|
||||||
|
func (mr *MockUseCaseMockRecorder) Update(e interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUseCase)(nil).Update), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method
|
||||||
|
func (m *MockUseCase) Delete(id entity.ID) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Delete", id)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete indicates an expected call of Delete
|
||||||
|
func (mr *MockUseCaseMockRecorder) Delete(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockUseCase)(nil).Delete), id)
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
//IRepo in memory repo
|
||||||
|
type IRepo struct {
|
||||||
|
m map[entity.ID]*User
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewInmemRepository create new repository
|
||||||
|
func NewInmemRepository() *IRepo {
|
||||||
|
var m = map[entity.ID]*User{}
|
||||||
|
return &IRepo{
|
||||||
|
m: m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create an user
|
||||||
|
func (r *IRepo) Create(e *User) (entity.ID, error) {
|
||||||
|
r.m[e.ID] = e
|
||||||
|
return e.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get an user
|
||||||
|
func (r *IRepo) Get(id entity.ID) (*User, error) {
|
||||||
|
if r.m[id] == nil {
|
||||||
|
return nil, domain.ErrNotFound
|
||||||
|
}
|
||||||
|
return r.m[id], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update an user
|
||||||
|
func (r *IRepo) Update(e *User) error {
|
||||||
|
_, err := r.Get(e.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.m[e.ID] = e
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Search users
|
||||||
|
func (r *IRepo) Search(query string) ([]*User, error) {
|
||||||
|
var d []*User
|
||||||
|
for _, j := range r.m {
|
||||||
|
if strings.Contains(strings.ToLower(j.FirstName), query) {
|
||||||
|
d = append(d, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(d) == 0 {
|
||||||
|
return nil, domain.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//List users
|
||||||
|
func (r *IRepo) List() ([]*User, error) {
|
||||||
|
var d []*User
|
||||||
|
for _, j := range r.m {
|
||||||
|
d = append(d, j)
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete an user
|
||||||
|
func (r *IRepo) Delete(id entity.ID) error {
|
||||||
|
if r.m[id] == nil {
|
||||||
|
return domain.ErrNotFound
|
||||||
|
}
|
||||||
|
r.m[id] = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Service service interface
|
||||||
|
type Service struct {
|
||||||
|
repo Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewService create new use case
|
||||||
|
func NewService(r Repository) *Service {
|
||||||
|
return &Service{
|
||||||
|
repo: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create an user
|
||||||
|
func (s *Service) Create(e *User) (entity.ID, error) {
|
||||||
|
e.ID = entity.NewID()
|
||||||
|
e.CreatedAt = time.Now()
|
||||||
|
return s.repo.Create(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get an user
|
||||||
|
func (s *Service) Get(id entity.ID) (*User, error) {
|
||||||
|
return s.repo.Get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Search users
|
||||||
|
func (s *Service) Search(query string) ([]*User, error) {
|
||||||
|
return s.repo.Search(strings.ToLower(query))
|
||||||
|
}
|
||||||
|
|
||||||
|
//List users
|
||||||
|
func (s *Service) List() ([]*User, error) {
|
||||||
|
return s.repo.List()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete an user
|
||||||
|
func (s *Service) Delete(id entity.ID) error {
|
||||||
|
_, err := s.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.repo.Delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update an user
|
||||||
|
func (s *Service) Update(e *User) error {
|
||||||
|
e.UpdatedAt = time.Now()
|
||||||
|
return s.repo.Update(e)
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Create(t *testing.T) {
|
||||||
|
repo := NewInmemRepository()
|
||||||
|
service := NewService(repo)
|
||||||
|
u := NewFixtureUser()
|
||||||
|
id, err := service.Create(u)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, u.ID, id)
|
||||||
|
assert.False(t, u.CreatedAt.IsZero())
|
||||||
|
assert.True(t, u.UpdatedAt.IsZero())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_SearchAndFind(t *testing.T) {
|
||||||
|
repo := NewInmemRepository()
|
||||||
|
service := NewService(repo)
|
||||||
|
u1 := NewFixtureUser()
|
||||||
|
u2 := NewFixtureUser()
|
||||||
|
u2.FirstName = "Lemmy"
|
||||||
|
|
||||||
|
uID, _ := service.Create(u1)
|
||||||
|
_, _ = service.Create(u2)
|
||||||
|
|
||||||
|
t.Run("search", func(t *testing.T) {
|
||||||
|
c, err := service.Search("ozzy")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 1, len(c))
|
||||||
|
assert.Equal(t, "Osbourne", c[0].LastName)
|
||||||
|
|
||||||
|
c, err = service.Search("dio")
|
||||||
|
assert.Equal(t, domain.ErrNotFound, err)
|
||||||
|
assert.Nil(t, c)
|
||||||
|
})
|
||||||
|
t.Run("list all", func(t *testing.T) {
|
||||||
|
all, err := service.List()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 2, len(all))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get", func(t *testing.T) {
|
||||||
|
saved, err := service.Get(uID)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, u1.FirstName, saved.FirstName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Update(t *testing.T) {
|
||||||
|
repo := NewInmemRepository()
|
||||||
|
service := NewService(repo)
|
||||||
|
u := NewFixtureUser()
|
||||||
|
id, err := service.Create(u)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
saved, _ := service.Get(id)
|
||||||
|
saved.FirstName = "Dio"
|
||||||
|
saved.Books = append(saved.Books, entity.NewID())
|
||||||
|
assert.Nil(t, service.Update(saved))
|
||||||
|
updated, err := service.Get(id)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "Dio", updated.FirstName)
|
||||||
|
assert.False(t, u.UpdatedAt.IsZero())
|
||||||
|
assert.Equal(t, 1, len(updated.Books))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
repo := NewInmemRepository()
|
||||||
|
service := NewService(repo)
|
||||||
|
u1 := NewFixtureUser()
|
||||||
|
u2 := NewFixtureUser()
|
||||||
|
u2ID, _ := service.Create(u2)
|
||||||
|
|
||||||
|
err := service.Delete(u1.ID)
|
||||||
|
assert.Equal(t, domain.ErrNotFound, err)
|
||||||
|
|
||||||
|
err = service.Delete(u2ID)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
_, err = service.Get(u2ID)
|
||||||
|
assert.Equal(t, domain.ErrNotFound, err)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
//User data
|
||||||
|
type User struct {
|
||||||
|
ID entity.ID
|
||||||
|
Email string
|
||||||
|
Password string
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
Books []entity.ID
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
//ErrNotFound not found
|
||||||
|
var ErrNotFound = errors.New("Not found")
|
||||||
|
|
||||||
|
//ErrCannotBeDeleted cannot be deleted
|
||||||
|
var ErrCannotBeDeleted = errors.New("Cannot Be Deleted")
|
||||||
|
|
||||||
|
//ErrNotEnoughBooks cannot borrow
|
||||||
|
var ErrNotEnoughBooks = errors.New("Not enough books")
|
||||||
|
|
||||||
|
//ErrBookAlreadyBorrowed cannot borrow
|
||||||
|
var ErrBookAlreadyBorrowed = errors.New("Book already borrowed")
|
||||||
|
|
||||||
|
//ErrBookNotBorrowed cannot return
|
||||||
|
var ErrBookNotBorrowed = errors.New("Book not borrowed")
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package loan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity/book"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
//UseCase use case interface
|
||||||
|
type UseCase interface {
|
||||||
|
Borrow(u *user.User, b *book.Book) error
|
||||||
|
Return(b *book.Book) error
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: domain/loan/interface.go
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
book "github.com/eminetto/clean-architecture-go-v2/domain/entity/book"
|
||||||
|
user "github.com/eminetto/clean-architecture-go-v2/domain/entity/user"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
reflect "reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockUseCase is a mock of UseCase interface
|
||||||
|
type MockUseCase struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockUseCaseMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockUseCaseMockRecorder is the mock recorder for MockUseCase
|
||||||
|
type MockUseCaseMockRecorder struct {
|
||||||
|
mock *MockUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockUseCase creates a new mock instance
|
||||||
|
func NewMockUseCase(ctrl *gomock.Controller) *MockUseCase {
|
||||||
|
mock := &MockUseCase{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockUseCaseMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockUseCase) EXPECT() *MockUseCaseMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borrow mocks base method
|
||||||
|
func (m *MockUseCase) Borrow(u *user.User, b *book.Book) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Borrow", u, b)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borrow indicates an expected call of Borrow
|
||||||
|
func (mr *MockUseCaseMockRecorder) Borrow(u, b interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Borrow", reflect.TypeOf((*MockUseCase)(nil).Borrow), u, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return mocks base method
|
||||||
|
func (m *MockUseCase) Return(b *book.Book) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Return", b)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return indicates an expected call of Return
|
||||||
|
func (mr *MockUseCaseMockRecorder) Return(b interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Return", reflect.TypeOf((*MockUseCase)(nil).Return), b)
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package loan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity/book"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Service service interface
|
||||||
|
type Service struct {
|
||||||
|
uService user.UseCase
|
||||||
|
bService book.UseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewService create new use case
|
||||||
|
func NewService(u user.UseCase, b book.UseCase) *Service {
|
||||||
|
return &Service{
|
||||||
|
uService: u,
|
||||||
|
bService: b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Borrow borrow a book to an user
|
||||||
|
func (s *Service) Borrow(u *user.User, b *book.Book) error {
|
||||||
|
u, err := s.uService.Get(u.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b, err = s.bService.Get(b.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b.Quantity <= 0 {
|
||||||
|
return domain.ErrNotEnoughBooks
|
||||||
|
}
|
||||||
|
for _, v := range u.Books {
|
||||||
|
if v == b.ID {
|
||||||
|
return domain.ErrBookAlreadyBorrowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.Books = append(u.Books, b.ID)
|
||||||
|
err = s.uService.Update(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.Quantity--
|
||||||
|
err = s.bService.Update(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return return a book
|
||||||
|
func (s *Service) Return(b *book.Book) error {
|
||||||
|
b, err := s.bService.Get(b.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
all, err := s.uService.List()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
borrowed := false
|
||||||
|
var borrowedBy entity.ID
|
||||||
|
for _, u := range all {
|
||||||
|
for _, bookID := range u.Books {
|
||||||
|
if bookID == b.ID {
|
||||||
|
borrowed = true
|
||||||
|
borrowedBy = u.ID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !borrowed {
|
||||||
|
return domain.ErrBookNotBorrowed
|
||||||
|
}
|
||||||
|
u, err := s.uService.Get(borrowedBy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i, j := range u.Books {
|
||||||
|
if j == b.ID {
|
||||||
|
u.Books = append(u.Books[:i], u.Books[i+1:]...)
|
||||||
|
err = s.uService.Update(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.Quantity++
|
||||||
|
err = s.bService.Update(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package loan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity"
|
||||||
|
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity/book"
|
||||||
|
bmock "github.com/eminetto/clean-architecture-go-v2/domain/entity/book/mock"
|
||||||
|
"github.com/eminetto/clean-architecture-go-v2/domain/entity/user"
|
||||||
|
umock "github.com/eminetto/clean-architecture-go-v2/domain/entity/user/mock"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Borrow(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
uMock := umock.NewMockUseCase(controller)
|
||||||
|
bMock := bmock.NewMockUseCase(controller)
|
||||||
|
service := NewService(uMock, bMock)
|
||||||
|
t.Run("user not found", func(t *testing.T) {
|
||||||
|
u := user.NewFixtureUser()
|
||||||
|
b := book.NewFixtureBook()
|
||||||
|
uMock.EXPECT().Get(u.ID).Return(nil, domain.ErrNotFound)
|
||||||
|
err := service.Borrow(u, b)
|
||||||
|
assert.Equal(t, domain.ErrNotFound, err)
|
||||||
|
})
|
||||||
|
t.Run("book not found", func(t *testing.T) {
|
||||||
|
u := user.NewFixtureUser()
|
||||||
|
b := book.NewFixtureBook()
|
||||||
|
uMock.EXPECT().Get(u.ID).Return(u, nil)
|
||||||
|
bMock.EXPECT().Get(b.ID).Return(nil, domain.ErrNotFound)
|
||||||
|
err := service.Borrow(u, b)
|
||||||
|
assert.Equal(t, domain.ErrNotFound, err)
|
||||||
|
})
|
||||||
|
t.Run("not enough books to borrow", func(t *testing.T) {
|
||||||
|
u := user.NewFixtureUser()
|
||||||
|
b := book.NewFixtureBook()
|
||||||
|
b.Quantity = 0
|
||||||
|
uMock.EXPECT().Get(u.ID).Return(u, nil)
|
||||||
|
bMock.EXPECT().Get(b.ID).Return(b, nil)
|
||||||
|
err := service.Borrow(u, b)
|
||||||
|
assert.Equal(t, domain.ErrNotEnoughBooks, err)
|
||||||
|
})
|
||||||
|
t.Run("book already borrowed", func(t *testing.T) {
|
||||||
|
u := user.NewFixtureUser()
|
||||||
|
b := book.NewFixtureBook()
|
||||||
|
u.Books = []entity.ID{b.ID}
|
||||||
|
b.Quantity = 1
|
||||||
|
uMock.EXPECT().Get(u.ID).Return(u, nil)
|
||||||
|
bMock.EXPECT().Get(b.ID).Return(b, nil)
|
||||||
|
err := service.Borrow(u, b)
|
||||||
|
assert.Equal(t, domain.ErrBookAlreadyBorrowed, err)
|
||||||
|
})
|
||||||
|
t.Run("sucess", func(t *testing.T) {
|
||||||
|
u := user.NewFixtureUser()
|
||||||
|
b := book.NewFixtureBook()
|
||||||
|
uMock.EXPECT().Get(u.ID).Return(u, nil)
|
||||||
|
bMock.EXPECT().Get(b.ID).Return(b, nil)
|
||||||
|
uMock.EXPECT().Update(u).Return(nil)
|
||||||
|
bMock.EXPECT().Update(b).Return(nil)
|
||||||
|
err := service.Borrow(u, b)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Return(t *testing.T) {
|
||||||
|
controller := gomock.NewController(t)
|
||||||
|
defer controller.Finish()
|
||||||
|
uMock := umock.NewMockUseCase(controller)
|
||||||
|
bMock := bmock.NewMockUseCase(controller)
|
||||||
|
service := NewService(uMock, bMock)
|
||||||
|
t.Run("book not found", func(t *testing.T) {
|
||||||
|
b := book.NewFixtureBook()
|
||||||
|
bMock.EXPECT().Get(b.ID).Return(nil, domain.ErrNotFound)
|
||||||
|
err := service.Return(b)
|
||||||
|
assert.Equal(t, domain.ErrNotFound, err)
|
||||||
|
})
|
||||||
|
t.Run("book not borrowed", func(t *testing.T) {
|
||||||
|
u := user.NewFixtureUser()
|
||||||
|
b := book.NewFixtureBook()
|
||||||
|
bMock.EXPECT().Get(b.ID).Return(b, nil)
|
||||||
|
uMock.EXPECT().List().Return([]*user.User{u}, nil)
|
||||||
|
err := service.Return(b)
|
||||||
|
assert.Equal(t, domain.ErrBookNotBorrowed, err)
|
||||||
|
})
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
u := user.NewFixtureUser()
|
||||||
|
b := book.NewFixtureBook()
|
||||||
|
u.Books = []entity.ID{b.ID}
|
||||||
|
bMock.EXPECT().Get(b.ID).Return(b, nil)
|
||||||
|
uMock.EXPECT().Get(u.ID).Return(u, nil)
|
||||||
|
uMock.EXPECT().List().Return([]*user.User{u}, nil)
|
||||||
|
uMock.EXPECT().Update(u).Return(nil)
|
||||||
|
bMock.EXPECT().Update(b).Return(nil)
|
||||||
|
err := service.Return(b)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
module github.com/eminetto/clean-architecture-go-v2
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/golang/mock v1.4.3
|
||||||
|
github.com/google/uuid v1.1.1
|
||||||
|
github.com/stretchr/testify v1.6.1
|
||||||
|
)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
||||||
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# my global config
|
||||||
|
global:
|
||||||
|
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
|
||||||
|
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
|
||||||
|
|
||||||
|
# Alertmanager configuration
|
||||||
|
alerting:
|
||||||
|
alertmanagers:
|
||||||
|
- static_configs:
|
||||||
|
- targets:
|
||||||
|
# - alertmanager:9093
|
||||||
|
|
||||||
|
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
|
||||||
|
rule_files:
|
||||||
|
|
||||||
|
# A scrape configuration containing exactly one endpoint to scrape:
|
||||||
|
# Here it's Prometheus itself.
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: bookmark
|
||||||
|
scrape_interval: 10s
|
||||||
|
static_configs:
|
||||||
|
- targets: ['host.docker.internal:8080']
|
||||||
|
- job_name: pushgateway
|
||||||
|
scrape_interval: 10s
|
||||||
|
static_configs:
|
||||||
|
- targets: ['host.docker.internal:9091']
|
||||||
Executable
+64
@@ -0,0 +1,64 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
//CLI define a CLI app
|
||||||
|
type CLI struct {
|
||||||
|
Name string
|
||||||
|
StartedAt time.Time
|
||||||
|
FinishedAt time.Time
|
||||||
|
Duration float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCLI create a new CLI app
|
||||||
|
func NewCLI(name string) *CLI {
|
||||||
|
return &CLI{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Started start monitoring the app
|
||||||
|
func (c *CLI) Started() {
|
||||||
|
c.StartedAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finished app finished
|
||||||
|
func (c *CLI) Finished() {
|
||||||
|
c.FinishedAt = time.Now()
|
||||||
|
c.Duration = time.Since(c.StartedAt).Seconds()
|
||||||
|
}
|
||||||
|
|
||||||
|
//HTTP application
|
||||||
|
type HTTP struct {
|
||||||
|
Handler string
|
||||||
|
Method string
|
||||||
|
StatusCode string
|
||||||
|
StartedAt time.Time
|
||||||
|
FinishedAt time.Time
|
||||||
|
Duration float64
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewHTTP create a new HTTP app
|
||||||
|
func NewHTTP(handler string, method string) *HTTP {
|
||||||
|
return &HTTP{
|
||||||
|
Handler: handler,
|
||||||
|
Method: method,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Started start monitoring the app
|
||||||
|
func (h *HTTP) Started() {
|
||||||
|
h.StartedAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finished app finished
|
||||||
|
func (h *HTTP) Finished() {
|
||||||
|
h.FinishedAt = time.Now()
|
||||||
|
h.Duration = time.Since(h.StartedAt).Seconds()
|
||||||
|
}
|
||||||
|
|
||||||
|
//UseCase definition
|
||||||
|
type UseCase interface {
|
||||||
|
SaveCLI(c *CLI) error
|
||||||
|
SaveHTTP(h *HTTP)
|
||||||
|
}
|
||||||
Executable
+55
@@ -0,0 +1,55 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/push"
|
||||||
|
"github.com/eminetto/clean-architecture-go/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Service implements UseCase interface
|
||||||
|
type Service struct {
|
||||||
|
pHistogram *prometheus.HistogramVec
|
||||||
|
httpRequestHistogram *prometheus.HistogramVec
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewPrometheusService create a new prometheus service
|
||||||
|
func NewPrometheusService() (*Service, error) {
|
||||||
|
cli := prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
|
Namespace: "pushgateway",
|
||||||
|
Name: "cmd_duration_seconds",
|
||||||
|
Help: "CLI application execution in seconds",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
}, []string{"name"})
|
||||||
|
http := prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
|
Namespace: "http",
|
||||||
|
Name: "request_duration_seconds",
|
||||||
|
Help: "The latency of the HTTP requests.",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
}, []string{"handler", "method", "code"})
|
||||||
|
|
||||||
|
s := &Service{
|
||||||
|
pHistogram: cli,
|
||||||
|
httpRequestHistogram: http,
|
||||||
|
}
|
||||||
|
err := prometheus.Register(s.pHistogram)
|
||||||
|
if err != nil && err.Error() != "duplicate metrics collector registration attempted" {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = prometheus.Register(s.httpRequestHistogram)
|
||||||
|
if err != nil && err.Error() != "duplicate metrics collector registration attempted" {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//SaveCLI send metrics to server
|
||||||
|
func (s *Service) SaveCLI(c *CLI) error {
|
||||||
|
gatewayURL := config.PROMETHEUS_PUSHGATEWAY
|
||||||
|
s.pHistogram.WithLabelValues(c.Name).Observe(c.Duration)
|
||||||
|
return push.New(gatewayURL, "cmd_job").Collector(s.pHistogram).Push()
|
||||||
|
}
|
||||||
|
|
||||||
|
//SaveHTTP send metrics to server
|
||||||
|
func (s *Service) SaveHTTP(h *HTTP) {
|
||||||
|
s.httpRequestHistogram.WithLabelValues(h.Handler, h.Method, h.StatusCode).Observe(h.Duration)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user