Compare commits
3 Commits
7add2af0c2
...
a130a826bc
Author | SHA1 | Date | |
---|---|---|---|
a130a826bc | |||
8e132dbdcd | |||
1a6ea59e92 |
@ -12,6 +12,7 @@ import (
|
||||
"git.nochill.in/nochill/hiling_go/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/lib/pq"
|
||||
ysqlc "github.com/yiplee/sqlc"
|
||||
)
|
||||
|
||||
type createLocationReq struct {
|
||||
@ -60,11 +61,21 @@ func (server *Server) createLocation(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
err := server.Store.CreateLocation(ctx, arg)
|
||||
|
||||
if err != nil {
|
||||
if pqErr, ok := err.(*pq.Error); ok {
|
||||
ctx.JSON(http.StatusConflict, ErrorResponse(err, fmt.Sprintf("Something went wrong, code: %s", pqErr.Code.Name())))
|
||||
switch pqErr.Code.Name() {
|
||||
case "foreign_key_violation", "unique_violation":
|
||||
if pqErr.Constraint == "locations_regency_id_fkey" {
|
||||
ctx.JSON(http.StatusConflict, ErrorResponse(err, fmt.Sprintf("Failed to submit location, there's no regency with id: %d", arg.RegencyID)))
|
||||
return
|
||||
}
|
||||
if pqErr.Constraint == "submitted_by_fkey" {
|
||||
ctx.JSON(http.StatusConflict, ErrorResponse(err, fmt.Sprintf("Failed to submit location, there's no user with id: %d", arg.SubmittedBy)))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||
return
|
||||
}
|
||||
@ -85,12 +96,12 @@ func (server *Server) getListLocations(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
arg := db.GetListLocationsParams{
|
||||
Limit: req.PageSize,
|
||||
Offset: (req.Page - 1) * req.PageSize,
|
||||
}
|
||||
locations, err := server.Store.GetListLocations(ysqlc.Build(ctx, func(builder *ysqlc.Builder) {
|
||||
builder.Limit(int(req.PageSize))
|
||||
builder.Offset(int(req.Page-1) * int(req.PageSize))
|
||||
builder.Order("created_at ASC")
|
||||
}))
|
||||
|
||||
locations, err := server.Store.GetListLocations(ctx, arg)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||
return
|
||||
@ -112,6 +123,10 @@ func (server *Server) getLocation(ctx *gin.Context) {
|
||||
|
||||
location, err := server.Store.GetLocation(ctx, req.ID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
ctx.JSON(http.StatusNotFound, ErrorResponse(err, ""))
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong"))
|
||||
return
|
||||
}
|
||||
|
@ -1,31 +1,166 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mockdb "git.nochill.in/nochill/hiling_go/db/mock"
|
||||
db "git.nochill.in/nochill/hiling_go/db/sqlc"
|
||||
"git.nochill.in/nochill/hiling_go/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestGetListLocationsAPI(t *testing.T) {
|
||||
func TestGetLocationsAPI(t *testing.T) {
|
||||
location := randomLocation()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
id int32
|
||||
buildStubs func(store *mockdb.MockStore)
|
||||
checkResponse func(t *testing.T, recorder *httptest.ResponseRecorder)
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
id: location.ID,
|
||||
buildStubs: func(store *mockdb.MockStore) {
|
||||
store.EXPECT().
|
||||
GetLocation(gomock.Any(), gomock.Eq(location.ID)).
|
||||
Times(1).
|
||||
Return(location, nil)
|
||||
},
|
||||
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
|
||||
require.Equal(t, http.StatusOK, recorder.Code)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testCases {
|
||||
tc := testCases[i]
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
store := mockdb.NewMockStore(ctrl)
|
||||
tc.buildStubs(store)
|
||||
|
||||
server := newTestServer(t, store)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
url := fmt.Sprintf("/location/%d", tc.id)
|
||||
request, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
server.Router.ServeHTTP(recorder, request)
|
||||
tc.checkResponse(t, recorder)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCreateLocationAPI(t *testing.T) {
|
||||
_ = db.CreateLocationParams{
|
||||
location := db.CreateLocationParams{
|
||||
Address: util.RandomString(10),
|
||||
Name: util.RandomString(10),
|
||||
SubmittedBy: int32(util.RandomInt(0, 10)),
|
||||
SubmittedBy: 1,
|
||||
RegencyID: 1305,
|
||||
GoogleMapsLink: sql.NullString{Valid: true, String: util.RandomString(10)},
|
||||
}
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
body db.CreateLocationParams
|
||||
buildStubs func(store *mockdb.MockStore)
|
||||
checkResponse func(t *testing.T, recorder *httptest.ResponseRecorder)
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
body: location,
|
||||
buildStubs: func(store *mockdb.MockStore) {
|
||||
arg := db.CreateLocationParams{
|
||||
Address: location.Address,
|
||||
Name: location.Name,
|
||||
SubmittedBy: location.SubmittedBy,
|
||||
RegencyID: location.RegencyID,
|
||||
GoogleMapsLink: location.GoogleMapsLink,
|
||||
}
|
||||
store.EXPECT().
|
||||
CreateLocation(gomock.Any(), gomock.Eq(arg)).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
},
|
||||
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
|
||||
require.Equal(t, http.StatusOK, recorder.Code)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Bad Request",
|
||||
body: db.CreateLocationParams{},
|
||||
buildStubs: func(store *mockdb.MockStore) {
|
||||
store.EXPECT().
|
||||
CreateLocation(gomock.Any(), gomock.Any()).
|
||||
Times(0)
|
||||
},
|
||||
checkResponse: func(t *testing.T, recorder *httptest.ResponseRecorder) {
|
||||
require.Equal(t, http.StatusBadRequest, recorder.Code)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testCases {
|
||||
tc := testCases[i]
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
bodyBuffer := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(bodyBuffer)
|
||||
mw.WriteField("address", tc.body.Address)
|
||||
mw.WriteField("name", tc.body.Name)
|
||||
mw.WriteField("submitted_by", fmt.Sprintf("%d", tc.body.SubmittedBy))
|
||||
mw.WriteField("regency_id", fmt.Sprintf("%d", tc.body.RegencyID))
|
||||
mw.WriteField("google_maps_link", tc.body.GoogleMapsLink.String)
|
||||
mw.Close()
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// store := mockdb.MockStore
|
||||
store := mockdb.NewMockStore(ctrl)
|
||||
tc.buildStubs(store)
|
||||
|
||||
server := newTestServer(t, store)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
url := "/locations"
|
||||
request, err := http.NewRequest(http.MethodPost, url, bodyBuffer)
|
||||
request.Header.Set("Content-Type", mw.FormDataContentType())
|
||||
require.NoError(t, err)
|
||||
|
||||
server.Router.ServeHTTP(recorder, request)
|
||||
tc.checkResponse(t, recorder)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func randomLocation() db.Location {
|
||||
return db.Location{
|
||||
ID: int32(util.RandomInt(1, 20)),
|
||||
Address: util.RandomString(10),
|
||||
Name: util.RandomString(10),
|
||||
GoogleMapsLink: sql.NullString{Valid: true, String: util.RandomString(20)},
|
||||
SubmittedBy: 1,
|
||||
TotalVisited: sql.NullInt32{Valid: true, Int32: int32(util.RandomInt(0, 32))},
|
||||
Thumbnail: sql.NullString{Valid: false, String: ""},
|
||||
RegencyID: 1305,
|
||||
IsDeleted: sql.NullBool{Valid: true, Bool: false},
|
||||
CreatedAt: sql.NullTime{Valid: true, Time: time.Now()},
|
||||
UpdatedAt: sql.NullTime{Valid: true, Time: time.Now()},
|
||||
ApprovedBy: sql.NullInt32{Valid: true, Int32: 1},
|
||||
ApprovedAt: sql.NullTime{Valid: true, Time: time.Now()},
|
||||
}
|
||||
}
|
||||
|
@ -65,18 +65,18 @@ func (mr *MockStoreMockRecorder) CreateUser(arg0, arg1 interface{}) *gomock.Call
|
||||
}
|
||||
|
||||
// GetListLocations mocks base method.
|
||||
func (m *MockStore) GetListLocations(arg0 context.Context, arg1 db.GetListLocationsParams) ([]db.Location, error) {
|
||||
func (m *MockStore) GetListLocations(arg0 context.Context) ([]db.Location, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetListLocations", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "GetListLocations", arg0)
|
||||
ret0, _ := ret[0].([]db.Location)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetListLocations indicates an expected call of GetListLocations.
|
||||
func (mr *MockStoreMockRecorder) GetListLocations(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockStoreMockRecorder) GetListLocations(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListLocations", reflect.TypeOf((*MockStore)(nil).GetListLocations), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListLocations", reflect.TypeOf((*MockStore)(nil).GetListLocations), arg0)
|
||||
}
|
||||
|
||||
// GetLocation mocks base method.
|
||||
|
@ -1,7 +1,5 @@
|
||||
-- name: GetListLocations :many
|
||||
SELECT * FROM locations
|
||||
LIMIT $1
|
||||
OFFSET $2;
|
||||
SELECT * FROM locations;
|
||||
|
||||
-- name: GetLocation :one
|
||||
SELECT * FROM locations
|
||||
|
@ -43,17 +43,10 @@ func (q *Queries) CreateLocation(ctx context.Context, arg CreateLocationParams)
|
||||
|
||||
const getListLocations = `-- name: GetListLocations :many
|
||||
SELECT id, address, name, google_maps_link, submitted_by, total_visited, thumbnail, regency_id, is_deleted, created_at, updated_at, approved_by, approved_at FROM locations
|
||||
LIMIT $1
|
||||
OFFSET $2
|
||||
`
|
||||
|
||||
type GetListLocationsParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetListLocations(ctx context.Context, arg GetListLocationsParams) ([]Location, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getListLocations, arg.Limit, arg.Offset)
|
||||
func (q *Queries) GetListLocations(ctx context.Context) ([]Location, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getListLocations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
type Querier interface {
|
||||
CreateLocation(ctx context.Context, arg CreateLocationParams) error
|
||||
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
||||
GetListLocations(ctx context.Context, arg GetListLocationsParams) ([]Location, error)
|
||||
GetListLocations(ctx context.Context) ([]Location, error)
|
||||
GetLocation(ctx context.Context, id int32) (Location, error)
|
||||
UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error
|
||||
UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error)
|
||||
|
@ -2,6 +2,8 @@ package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/yiplee/sqlc"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
@ -16,7 +18,7 @@ type SQLStore struct {
|
||||
func NewStore(db *sql.DB) Store {
|
||||
return &SQLStore{
|
||||
db: db,
|
||||
Queries: New(db),
|
||||
Queries: New(sqlc.Wrap(db)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestGetLocationsList(t *testing.T) {
|
||||
arg := db.GetListLocationsParams{
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
locations, err := testQueries.GetListLocations(context.Background(), arg)
|
||||
locations, err := testQueries.GetListLocations(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, locations)
|
||||
}
|
||||
|
3
go.mod
3
go.mod
@ -45,10 +45,13 @@ require (
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/yiplee/nap v1.0.1 // indirect
|
||||
github.com/yiplee/sqlc v1.0.2 // indirect
|
||||
go.uber.org/mock v0.2.0 // indirect
|
||||
golang.org/x/arch v0.5.0 // indirect
|
||||
golang.org/x/crypto v0.13.0 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
|
8
go.sum
8
go.sum
@ -176,6 +176,7 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@ -230,6 +231,10 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yiplee/nap v1.0.1 h1:5p8KAIkYy+PIMGSk+ScF13Hh/OFkIEBHPuD14OFvStg=
|
||||
github.com/yiplee/nap v1.0.1/go.mod h1:7Zvro/en8ARhkqgv3vpj037yJSBRvGeNyj5Np5XUFgc=
|
||||
github.com/yiplee/sqlc v1.0.2 h1:GcWRpoKb0jRPuaYhiXex/LhJWkiCWQNnUjgOMQHgvJQ=
|
||||
github.com/yiplee/sqlc v1.0.2/go.mod h1:jZaVoaxbMKXkjmZAl1uOyBKf71/rhIStVcoZrg68X3c=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@ -349,6 +354,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
16
sqlc.yaml
16
sqlc.yaml
@ -1,10 +1,12 @@
|
||||
version: "1"
|
||||
packages:
|
||||
- name: "db"
|
||||
path: "./db/sqlc"
|
||||
queries: "./db/queries/"
|
||||
schema: "./db/migrations/"
|
||||
engine: "postgresql"
|
||||
version: "2"
|
||||
sql:
|
||||
- engine: "postgresql"
|
||||
queries: "./db/queries"
|
||||
schema: "./db/migrations"
|
||||
gen:
|
||||
go:
|
||||
package: "db"
|
||||
out: "./db/sqlc"
|
||||
emit_json_tags: true
|
||||
emit_prepared_queries: false
|
||||
emit_interface: true
|
||||
|
Loading…
Reference in New Issue
Block a user